일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 백엔드
- CS
- web server
- computer science
- spring cloud
- 영속성 컨텍스트
- ORM
- CI/CD
- 웹 서버
- 스프링 시큐리티
- Spring Security
- 스프링 배치
- 배포
- HTTP
- vm
- Container
- 자바
- JPA
- 컨테이너
- 스프링
- mysql
- Spring
- spring batch
- 스프링 부트
- Java
- virtualization
- 가상화
- 데이터베이스
- 도커
- spring boot
- Today
- Total
개발 일기
[Spring Boot] Soft Delete시 연관관계 엔티티 처리 (다중 논리 삭제 처리) 본문
전 게시글에서 특정 엔티티의 데이터를 제거 시에 Hard Delete하는 방법과 Soft Delete하는 방법 이렇게 두가지가 있고 Soft Delete를 하기 위해서 @SQLDelete와 @SQLRestriction을 통해 구현 가능하다는 사실을 알게 됐다.
그런데 만약 예를들어서 내가 멤버를 제거한다고 했을 때 그 연관관계에 있는 댓글들 즉, 해당 탈퇴 처리한 멤버의 댓글들은 어떻게 처리할지에 대한 고민이 생기기 시작했다. 그래서 해당 해결 후 이 블로그 글을 작성하게됐다.
첫 번째 방법 - @SQLDelete / @SQLRestriction
이 방법은 똑같이 Comment에도 위와 같은 어노테이션을 걸어두는 것이다.
// Member Entity
// ... 기타 anotation 생략
@SQLDelete(sql = "UPDATE member SET is_deleted = true, deleted_at = NOW() WHERE id = ?")
@SQLRestriction("is_deleted = false")
public class Member extends BaseTimeEntity {
@OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE)
private List<Comment> comments = new ArrayList<>();
@Column(name = "is_deleted", columnDefinition = "BOOLEAN DEFAULT false")
private Boolean isDeleted = false;
@Column(name = "deleted_at")
private LocalDateTime deleteAt;
}
// Comment Entity
// ... 기타 anotation 생략
@SQLDelete(sql = "UPDATE comment SET is_deleted = true, deleted_at = NOW() WHERE id = ?")
@SQLRestriction("is_deleted = false")
public class Comment extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@Column(name = "is_deleted", columnDefinition = "BOOLEAN DEFAULT false")
private Boolean isDeleted = false;
@Column(name = "deleted_at")
private LocalDateTime deleteAt;
}
위와 같이 연관관계 처리가 되어있을 시에 만약 Soft Delete를 하지 않으면 Cascade를 통해 연관관계에 있는 해당 멤버의 댓글들도 모두 Delete 된다. 하지만 현재 상황은 @SQLDelete / @SQLRestriction을 통해 논리 삭제를 하고 있기 때문에 Comment에 Delete 명령이 날라가지 않기 위해 Member엔티티와 마찬가지로 @SQLDelete / @SQLRestriction을 붙혀준다.
그래서 member_id가 1인 멤버 객체를 삭제 했더니 아래와 같이 member_id가 4인 멤버가 작성한 댓글들도 모두 삭제됐다.
그러나 이때 문제점을 발견했다.
나처럼 댓글과 같이 일대다 관계에 있는 엔티티에 대한 Soft Delete처리의 경우 다 쪽의 엔티티 수가 하나가 아닌 여러개일 경우 그 수만큼 Update 쿼리가 발생한 것을 확인할 수 있었다.
그래서 나 또한 member_id가 4인 member가 작성한 댓글이 5개라서 총 5개의 쿼리가 발생했다. 즉, Update가 단건으로 밖에 처리가 안되는 문제점이 있었다.
두 번째 방법 - 더티 체킹(Dirty Checking) 활용
고민을 해보다가 더티 체킹을 적용해봤다.
// Comment Entity
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
//@SQLDelete(sql = "UPDATE comment SET is_deleted = true, deleted_at = NOW() WHERE id = ?")
@SQLRestriction("is_deleted = false")
public class Comment extends BaseEntity {
// ...
public void commentSoftDelete() {
this.isDeleted = true;
}
}
// MemberService
public void softDeleteMemberV1(Long memberId) {
Member member = memberRepository.findMemberByIdOrThrow(memberId);
List<Comment> comments = member.getComments();
comments.forEach(Comment::commentSoftDelete);
memberRepository.delete(member);
}
혹시 더티 체킹을 하면 한번에 Update 문이 나가지 않을 까 하는 생각에 시도해봤다.
그러나 이번에도 쿼리가 하나씩 다 나갔다. 그러므로 실패
JPA 더티체킹 및 영속성 컨텍스트에 대한 이해가 부족했던것같다. 추가적인 공부가 필요할 것같음
"세 번째 방법 - JPQL IN을 통해 처리"
위와 같은 방법을 시도해 봤더니 안돼서 결국 JPQL을 통해 직접 쿼리를 짜서 다중 논리 삭제를 처리해야겠다는 생각이 들었다.
결과는 드디어 성공이였다 ㅎ
// CommentRepository
@Modifying
@Query("UPDATE Comment c SET c.isDeleted = true WHERE c.isDeleted = false AND c.id IN :commentIds")
void softDeleteComments(List<Long> commentIds);
// MemberService
public void softDeleteMemberV2(Long memberId) {
Member member = memberRepository.findMemberByIdOrThrow(memberId);
List<Long> commentIds = member.getComments().stream()
.map(Comment::getId)
.collect(Collectors.toList());
commentRepository.softDeleteComments(commentIds);
memberRepository.delete(member);
}
위와 같이 JPQL을 통해 Comment JpaRepository에다가 메서드를 추가해주고 MemberSerivce에서 삭제하려는 멤버의 Comment들의 id를 모아 한번에 JPQL IN절을 통해 UPDATE 한방 쿼리로 처리할 수 있었다.
쿼리가 N개 나갈 것을 하나로 줄일 수 있었다.
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot] orphanRemoval = true 고아 객체 관리 (1) | 2024.06.03 |
---|---|
[Spring Boot] 연관관계 영속성 전이(CASCADE) - REMOVE, PERSIST, ALL을 중심으로 (1) | 2024.06.03 |
[Spring Boot] Soft Delete(논리 삭제) 도입 - @SQLDelete & @SQLRestriction (0) | 2024.06.02 |
[Spring Boot] ModelMapper와 MapStruct (0) | 2024.05.31 |
[Spring Boot] 예외 처리(Exception Handling) Reference (0) | 2024.05.27 |