김영한님의 자바 ORM 표준 JPA 프로그래밍 책을 공부하면서

Casecade 의 정확한 동작 정의를 잘 모르겠다고 생각했다.

그래서 실습을 통해 테스트해보면서 기능을 정의해보았다.

 

이 글은 cascade 가 언제 주로 사용되는지와는 무관하게, 어떻게 동작하는지에 대해 정확히 이해하는 것을 목적으로 한다.

책에는 부모 엔티티에 적용하는 예제만 나오는데, 사실 cascade 옵션은 자식 엔티티에도 적용할 수 있다.

 

하지만 Casecade.REMOVE, orphanRemoval의 경우

적용하기 좋은 상황 자체가 거의 항상 부모 엔티티에 적용하는 상황이 되는 것 같다.

그래서 책에서도 부모 엔티티에 적용하는 상황만 소개했다고 생각한다.

 

용어 정의

foreign key 를 갖는 테이블의 엔티티를 자식 엔티티라고 하고, 반대쪽을 부모 엔티티라고 하자.
(자식 엔티티가 부모 엔티티를 참조함)


1. CascadeType.PERSIST

엔티티의 어떤 연관필드에 cascade = CascadeType.PERSIST 가 지정되어있으면,
엔티티를 persist 할 때

연관 필드들도 자동으로 persist 한다.

  • 자식 엔티티에 적용시 : 연관필드 먼저 자동으로 persist 한 뒤 엔티티를 persist 한다.
@Entity
public class User { // Level을 참조하는 자식 엔티티
    ...
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    @JoinColumn(name = "level", nullable = false)
    private Level level;
    ...
}
private static void logic(EntityManager entityManager) {
    Level vip = new Level(null, "normal", 0.0);
    // 부모인 Level 을 persist 하지 않았음

    User user1 = new User(null, "test1@email.com", vip); // 3번째 파라미터에서 부모와 연결
    entityManager.persist(user1); // 부모인 vip 객체를 자동으로 persist 해준 뒤 user1을 persist
}
Hibernate: /* Level 객체가 먼저 insert 된 것을 알 수 있다!! */
    insert 
        into
            Level
            (id, discountRate, name) 
        values
            (null, ?, ?)
Hibernate: 
    insert 
        into
            User
            (id, email, level) 
        values
            (null, ?, ?)
  • 부모 엔티티에 적용시 : 엔티티를 persist 한 다음에 연관필드들도 자동으로 persist 한다. (예제가 책에 자세히 나와있으므로 생략)
@Entity
public class Level {
    ...
    @OneToMany(mappedBy = "level", fetch = FetchType.LAZY, cascade = CasecadeType.PERSIST)
    private List<User> users = new ArrayList<>();
    ...
}

2. CasecadeType.REMOVE

엔티티의 어떤 연관필드에 cascade = CascadeType.REMOVE 가 지정되어있으면,
엔티티를 remove 할 때
연관필드자동으로 remove 된다.

  • 자식 엔티티에 적용 : 엔티티를 remove 한 뒤 연관필드자동으로 remove 한다.
@Entity
public class User { // Level을 참조하는 자식 엔티티
    ...
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    @JoinColumn(name = "level", nullable = false)
    private Level level; 
    ...
}
private static void logic(EntityManager entityManager) {
    Level vip = new Level(null, "VIP", 5.0);
    entityManager.persist(vip);

    User user1 = new User(null, "test2@email.com", vip);
    entityManager.persist(user2);

    entityManager.remove(user1);
    // 레벨 vip 을 지우는 코드가 없음
}
Hibernate: 
    insert 
        into
            Level
            (id, discountRate, name) 
        values
            (null, ?, ?)
Hibernate: 
    insert 
        into
            User
            (id, email, level) 
        values
            (null, ?, ?)
Hibernate: 
    delete 
        from
            User 
        where
            id=?
Hibernate: /* Level에 대한 delete가 실행된 것을 알 수 있다. */
    delete 
        from
            Level
        where
            id=?
  • 부모 엔티티 적용 - 연관 필드들을 자동으로 remove 한 다음 엔티티를 remove 한다. (예제가 책에 자세히 나와있으므로 생략)
@Entity
public class Level {
    ...
    @OneToMany(mappedBy = "level", fetch = FetchType.LAZY, cascade = CasecadeType.REMOVE)
    private List<User> users = new ArrayList<>();
    ...
}

3. orphanRemoval

댓글(Comment) & 글(Article) 로 예를 들어보자.
댓글은 반드시 하나의 글에 속한다고 하자.

즉, Comment 객체가 Article 객체를 반드시 한 개 참조하는 형태의 도메인이다.

 

Article 에서 다음과 같이

comments연관필드에 orphanRemoval = true 를 걸면

어떤 효과가 나타나는지를 정리해보자.

@Entity
public class Article {
    ...
    @OneToMany(mappedBy = "article", fetch = FetchType.LAZY, orphanRemoval = true)
    @Getter(AccessLevel.NONE)
    private List<Comment> comments = new ArrayList<>();
    ...
}

 

1.
개발자가 모종의 이유로
Comment 객체의 Article 참조를 제거하는 로직을 작성했다면?

소속된 글이 없는 댓글을 놔둘것인가??

이 때 고아가 된 Comment 객체를 자동으로 삭제해준다.

 

2.
CascadeType.REMOVE 와 마찬가지로
글이 삭제된 경우 (글 = Article = 부모)
자식이 부모와의 연관을 잃게 된 것과 마찬가지이므로
orphanRemoval = true 사용시 댓글이 자동으로 삭제된다. (=== CascadeType.REMOVE)

+ Recent posts