김영한님의 자바 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)
'JPA' 카테고리의 다른 글
[JPA] OneToMany join 데이터 중복 조회 문제 (0) | 2022.01.12 |
---|---|
[JPA] 다대다를 일대다, 다대일로 풀어서 구현할 때 조회기능 구현 (0) | 2021.07.14 |
[JPA] Gradle + H2 JPA 실습환경 셋팅 (1) | 2021.04.26 |