일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 백엔드
- 영속성 컨텍스트
- 스프링
- 스프링 부트
- web server
- computer science
- spring boot
- HTTP
- CS
- Spring
- spring batch
- 데이터베이스
- 스프링 시큐리티
- Java
- 자바
- 컨테이너
- ORM
- 스프링 배치
- vm
- 웹 서버
- spring cloud
- JPA
- CI/CD
- 도커
- Spring Security
- 가상화
- virtualization
- 배포
- mysql
- Container
- Today
- Total
개발 일기
[Spring Boot] 연관관계 영속성 전이(CASCADE) - REMOVE, PERSIST, ALL을 중심으로 본문
[Spring Boot] 연관관계 영속성 전이(CASCADE) - REMOVE, PERSIST, ALL을 중심으로
개발 일기장 주인 2024. 6. 3. 01:01김영한님 JPA 강의를 들으면서 연관관계 영속성 전이(CASCADE)라는 것에 대해 처음 듣게 됐었는데 실제로 Spring Boot 프로젝트를 하면서 Entity 연관관계를 맺을 때 참고 코드를 보고 따라 쓰고 있으면서 또 Soft Delete할때도 관련 작업을 해야해서 좀 대충 감은 잡았지만 그래도 정확히 알고 써야지 추후에 에러나 좀더 효과적인 개발을 할 수 있을 것 같아 정리하게 됐다.
영속성 전이(CASCADE)가 뭔데?
말 그대로 영속성의 전이이다. 연관관계를 맺는 두가지 엔티티가 있을 때 특정 엔티티를 영속 상태로 만들면 그 엔티티와 연관관계에 있는 엔티티 또한 영속 상태로 만들때 사용한다.
cascade옵션은 JPA에서 @ManyToOne과 @OneToMany관계에서 사용된다.
영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없고
엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함 을 제공할 뿐이다.
Cascade 타입은 열거형 타입으로 다음과 같다.
- ALL
: 부모 엔티티에서 자식 엔티티로 발생하는 모든 작업을 전파한다. 즉, 아래의 5가지 타입을 모두 포함하고 있기에 부모 엔티티와 자식엔티티의 생명주기가 같다. - PERSIST : CascadeType.PERSIST는 부모 엔티티에서 자식 엔티티로 영속화 작업을 전파한다.
- MERGE : Merge 작업은 주어진 객체의 상태를 같은 식별자를 가진 영속된 객체에 복사하는 작업이다.
- REMOVE : CascadeType.REMOVE는 부모에서 자식 엔티티까지 제거 작업을 전파한다.
- REFRESH : 부모 엔티티가 변경될 때마다 자식 엔티티를 데이터베이스에서 다시 읽어온다.
- DETACH : 부모 엔티티가 DETACH되면 자식 엔티티도 영속 컨텍스트에서 떨어진다.
이중 PERSIST, REMOVE를 예시를 통해 조금 더 깊이 이해해보자.
CascadeType.PERSIST
이 경우에 게시글이 생성되면 무조건 Default로 댓글 하나가 같이 생성된다고 가정해보자.
// 게시글 엔티티
@Entity
public class Content {
@OneToMany(mappedBy = "content", cascade = CascadeType.PERSIST)
// @OneToMany(mappedBy = "content")
private List<Comment> comments = new ArrayList<>();
@Builder
public Content(String title, String contentText, Member member) {
this.title = title;
this.contentText = contentText;
this.member = member;
}
public void addComment(Comment comment) {this.comments.add(comment);}
}
// 댓글 엔티티
@Entity
public class Comment {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "content_id")
private Content content;
}
// ContentCommandService - 게시글 생성 로직
public void createContent(Long memberId, ContentCreateRequestDto contentCreateRequestDto) {
String title = contentCreateRequestDto.title();
String text = contentCreateRequestDto.contentText();
Member member = memberRepository.findMemberByIdOrThrow(memberId);
Content content = Content.builder()
.member(member)
.title(title)
.contentText(text)
.build();
Comment comment = Comment.builder()
.commentText("Cascade Test")
.member(member)
.content(content)
.build();
content.addComment(comment);
contentRepository.save(content);
}
만약 위에 PERSIST가 아니라 주석대로 cascade설정이 없었다면? 아래 ContentService에서 연관관계에 있는 자식 엔티티인 comment는 content가 save될때 같이 영속화되지 못한다. 그렇기 때문에 content에 comment를 넣어줘서 같이 영속성 컨텍스트에 올라간다고 생각해도 실제론 그렇지않다.
그래서 CascadeType을 지정해주지 않으면 해당 게시글을 save할때 Default 댓글이 같이 저장되지 않는다.
그러나 CascadeType을 PERSIST로 지정한 경우에만 아래와 같이 content와 comment 테이블 모두로 INSERT쿼리가 나가고 실제로 댓글도 저장된다.
즉, 정리해보면 부모 엔티티를 영속화하게 되면 자식 엔티티도 같이 영속화 시킨다.
CascadeType.REMOVE
이번엔 REMOVE이다. 똑같이 위 엔티티에서 CascadeType을 REMOVE로 지정해보자.
우선 REMOVE로 지정한 상태에서 http://localhost:8080/api/v1/contents/11를 통해 id가 11인 게시글을 삭제해보면 연관된 댓글이 보두 삭제된 것을 확인할 수 있다.
그러나 REMOVE가 아니라 아예 CascadeType을 지정하지 않으면 어떻게 되는지 확인해보자.
다음과 같은 에러가 발생했다. 이 에러는 외래 키 제약 조건에 의해 발생한 것으로 content 엔티티를 삭제하려고 할 때, 해당 content와 연관된 comment가 데이터베이스에 남아있어서 삭제가 불가능하다는 에러이다.
즉, CascadeType.REMOVE를 지정하지 않게 되면 연관관계에 있는 자식 엔티티를 같이 제거하지 않고 부모 엔티티만 딱 제거한다는 사실을 확인해볼 수 있다.
그래서 언제 뭐 쓰는데? - 예시 들어보기
REMOVE 같은 경우 게시글과 그에 딸린 댓글들을 예시로 들어보면 게시글과 댓글 자체는 생성되는 시점은 다를 수 있지만 게시글이 삭제되면 댓글도 삭제되어야 하기 때문에 REMOVE 타입을 사용하면 좋을 것 같다.
PERSIST 같은 경우 Member Entity의 Address Entity를 포함시킬때 쓸 수 있을 것같다. 처음에 저장할때 Address 엔티티를 만들고 넣어준 다음에 Member를 저장할때 Address 엔티티도 같이 저장되어야하기 때문이다.
ALL 같은 경우 게시글 엔티티와 이미지 엔티티가 분리된 경우 둘 사이의 연관관계 매핑시에 ALL로 지정하면 좋을 것 같다. 이러한 사진의 경우 게시글이 생성될 때 함께 생성되고 삭제될 때 함께 삭제되기 때문이다.
그외의 조건들은 실제로 쓸일이 많을지 모르겠다. 실제로 구현을 하면서 그 외의 타입인 DETACH, MERGE, REFRESH를 쓰게되면 추가로 정리해보겠다.
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot] Entity에서의 올바른 롬복(Lombok) 사용에 있어서 나의 생각 정리 (1) | 2024.06.03 |
---|---|
[Spring Boot] orphanRemoval = true 고아 객체 관리 (1) | 2024.06.03 |
[Spring Boot] Soft Delete시 연관관계 엔티티 처리 (다중 논리 삭제 처리) (0) | 2024.06.02 |
[Spring Boot] Soft Delete(논리 삭제) 도입 - @SQLDelete & @SQLRestriction (0) | 2024.06.02 |
[Spring Boot] ModelMapper와 MapStruct (0) | 2024.05.31 |