[JPA] CascadeType.DELETE, orphanRemoval = true 차이

wlsh44·2023년 2월 26일
0

JPA를 공부하면서 두 개념이 많이 헷갈려서 정리를 하게 되었습니다.

엔티티 정의

우선 맴버와 게시글이 일대다 양방향 관계에 있다고 가정을 하겠습니다.

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "member")
    private List<Post> posts = new ArrayList<>();
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    public Post(Member member) {
        this.member = member;
    }
}

부모 엔티티 제거의 경우

우선 결과부터 말하자면 부모 엔티티를 제거할 경우, CascadeType.DELETEorphanRemoval = true 둘 다 자식 엔티티가 제거가 됩니다.

CascadeType.DELETE

//Member.java
@OneToMany(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.DELETE})
private List<Post> posts = new ArrayList<>();

@Test
@DisplayName("CascadeType.DELETE인 경우, 부모 엔티티(Member)가 제거되면 자식 엔티티(Post)도 제거되어야 함")
void test() throws Exception {
    //given
    Member member = new Member();
    Post post = new Post(member);
    member.addPost(post);
    member = memberRepository.save(member);

    //when
    memberRepository.delete(member);

    //then
    List<Post> posts = postRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(posts).hasSize(0);
    assertThat(members).hasSize(0);
}

orphanRemoval = true

//Member.java
@OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();

@Test
@DisplayName("orphanRemoval = true 인 경우, 부모 엔티티(Member)가 제거되면 자식 엔티티(Post)도 제거되어야 함")
void test() throws Exception {
    //given
    Member member = new Member();
    Post post = new Post(member);
    member.addPost(post);
    member = memberRepository.save(member);

    //when
    memberRepository.delete(member);

    //then
    List<Post> posts = postRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(posts).hasSize(0);
    assertThat(members).hasSize(0);
}

부모 엔티티에서 자식 엔티티 참조를 지울 경우

이 경우에서 두 옵션이 차이를 가집니다. 우선 테스트 코드부터 확인해보겠습니다.

CascadeType.DELETE

//Member.java
@OneToMany(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.DELETE})
private List<Post> posts = new ArrayList<>();

@Test
@DisplayName("CascadeType.DELETE인 경우, 부모 엔티티(Member)와 관계가 끊겨도 자식 엔티티(Post)는 제거되지 않음")
void test() throws Exception {
    //given
    Member member = new Member();
    Post post = new Post(member);
    member.addPost(post);
    member = memberRepository.save(member);

    //when
    member.getPosts().remove(0);

    //then
    List<Post> posts = postRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(posts).hasSize(1); // post size가 1이어야 함
    assertThat(members).hasSize(1);
}
Hibernate: 
    insert 
    into
        member
        (id) 
    values
        (default)
Hibernate: 
    insert 
    into
        post
        (id, member_id) 
    values
        (default, ?)
Hibernate: 
    select
        post0_.id as id1_2_,
        post0_.member_id as member_i2_2_ 
    from
        post post0_
Hibernate: 
    select
        member0_.id as id1_1_ 
    from
        member member0_

쿼리 결과를 보시면 맨 처음 Member와 Post 등록 insert 쿼리, 마지막 테스트 검증을 위한 select 쿼리 외에 어떤 쿼리도 나가지 않았습니다.

orphanRemoval = true

//Member.java
@OneToMany(mappedBy = "member", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();

@Test
@DisplayName("orphanRemoval = true 인 경우, 부모 엔티티(Member)와 관계가 끊기면 자식 엔티티(Post)는 제거되어야 함")
void test() throws Exception {
    //given
    Member member = new Member();
    Post post = new Post(member);
    member.addPost(post);
    member = memberRepository.save(member);

    //when
    member.getPosts().remove(0);

    //then
    List<Post> posts = postRepository.findAll();
    List<Member> members = memberRepository.findAll();

    assertThat(posts).hasSize(0); // post size가 0이어야 함
    assertThat(members).hasSize(1);
}
Hibernate: 
    insert 
    into
        member
        (id) 
    values
        (default)
Hibernate: 
    insert 
    into
        post
        (id, member_id) 
    values
        (default, ?)
Hibernate: 
    delete 
    from
        post 
    where
        id=?
Hibernate: 
    select
        post0_.id as id1_2_,
        post0_.member_id as member_i2_2_ 
    from
        post post0_
Hibernate: 
    select
        member0_.id as id1_1_ 
    from
        member member0_

CascadeType.DELETE와는 다르게 전체 쿼리에서 게시글을 지우는 delete 쿼리가 중간에 있는 것을 볼 수 있습니다.

정리

orphanRemoval = true는 고아 객체를 지우는 옵션입니다. 그렇기 때문에 첫 번째 테스트였던 부모 엔티티(Member)가 아예 삭제가 되면 당연히 자식 엔티티(Post)는 고아가 되고, 해당 옵션의 기능이 수행된 것입니다.
반면에 CascadeType.DELETE 옵션은 해당 엔티티가 em.remove() 같이 실제 엔티티가 삭제가 될 때 그 옵션이 붙은 엔티티에 삭제라는 영속성이 전이가 된 것이기 때문에 단순히 부모 엔티티에서 자식 엔티티의 참조를 제거하는 것으로는 엔티티가 삭제가 되지 않습니다.

부모 엔티티 제거부모 엔티티에서 자식 엔티티 참조 제거
CascadeType.DELETE자식 제거됨자식 제거 안 됨
orphanRemoval = true자식 제거됨자식 제거됨

참고

자바 ORM 표준 JPA 프로그래밍 - 기본편(강의)
자바 ORM 표준 JPA 프로그래밍

profile
정리정리

0개의 댓글