영속성 전이: CASCADE
=> 특정 엔티티와 또 다른 엔티티가 종속관계이고 영속상태, 삭제상태 등 라이프 사이클을 어느정도 일치 시키고 싶을 때 사용하면 좋은 방법이다.
@OneToMany, @OneToOne, @ManyToOne
어노테이션 등에 속성으로 cascade를 두고 CascadeType을 설정해주면됨
CascadeType 종류
CascadeType.ALL: 모두 적용 (연관관계의 엔티티가 모든 라이프 사이클이 맞물린다면)
CascadeType.PERSIST: 영속할때 만 적용
=>특정 엔티티를 영속 상태(persist)로 만들 때 연관된 엔티티도 함께 자동으로 영속상태로 만들고 싶은경우 활용
CascadeType.REMOVE: 삭제할때만 적용
아래 세개는 잘 안씀
CascadeType.MERGE
CascadeType.REFRESH
CascadeType.DETACH
주의
- 영속성 전이는 연관관계 매핑과 아무런 관련이 없다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함만 제공한다.
- 만약 관계가 설정된 두 엔티티가 있고, 만약 이 엔티티중 하나가 다른 엔티티와 다시 관계가 설정되어 있는 경우. 쓰면안된다.
주로 단일 엔티티에 종속적인 경우 사용하고 그렇지 않은경우 쓰지 말아야한다.
꼭 종속적이고(단일 소유자 일때), 라이프 사이클이 유사할때 사용하는 것을 추천한다.
ex) Parent 엔티티와 child 엔티티가 있는데 (종속 관계 1:N) child 엔티티가 다른 엔티티와도 관계가 설정이 되어 있는경우
코드 예제
Parent
package jpabook.jpashop.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
getChildList().add(child);
child.setParent(this);
}
public List<Child> getChildList() {
return childList;
}
public void setChildList(List<Child> childList) {
this.childList = childList;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@OneToMany 어노테이션에 cascade = CascadeType.PERSIST 로 설정되어 있는 것을 확인할 수 있다.
자식과 양방향 매핑을 위해 mappedBy 속성까지 설정한 모습이다.
Child
package jpabook.jpashop.domain;
import javax.persistence.*;
@Entity
public class Child {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
JPAMain
package jpabook.jpashop;
import jpabook.jpashop.domain.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.locks.Lock;
public class JpaMain {
public static void main(String[] args) {
// start
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랙잭션
tx.begin();
// code
try {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.flush();
em.clear();
// child1 삭제
Child findChild = em.find(Child.class, child1.getId());
em.remove(findChild);
// 확인
Parent findParent = em.find(Parent.class, parent.getId());
List<Child> childList = findParent.getChildList();
for (Child child : childList) {
System.out.println("child1.id = " + child.getId());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close(); // 사용 다하면 꼭 닫아줘야됨
}
// end
emf.close();
}
}
parent에서 cascade = CascadeType.PERSIST 를 설정하고 persist했기 때문에 child1, child2도 덩달아 persist가 완료되었을 것이다.
그리고 나는 실험을 위해 영속성 컨텍스트를 다시 초기화 시킨 후 child1을 삭제해보았다. 그 결과 child1은 전혀 삭제가 되지 않았다.
(1) em.remove(child1) 했을 때
이를 확인하고 CascadeType.PERSIST는 영속성 뿐만 아니라 '삭제와 관련된 생명주기까지도 따라가는구나, 정말 종속된 엔티티가 아니면 조심해야 겠다'고 생각했다.
(2) List<Child> childList = findParent.getChildList()
childList.remove(0);
궁금증이 생겨서 부모가 설정한 양방햔 관계의 childList에서 자식 엔티티를 하나 제거하고 실행해 봤다
테이블 상에서는 제거가 안되지만, 리스트의 자식 객체 하나는 삭제되었다. 이는 데이터 무결성이 맞지 않아서 리스트에서 제거할때
-> child객체의 parent 속성을 null로 만들던가
-> 뒤에 배울 orphanRemoval 속성으로 자식 객체도 자동 삭제되도록 할 것인가
에 대한 설정을 하는 것이 좋다고 생각했다. 관계만 끊어지고 자식객체가 남아 있다면 null로 만드는 것이 나을 것이고, 완전 종속되어 부모객체의 리스트에서 삭제하면 자식객체의 데이터도 완전 삭제되도록 로직을 구현할 것인가는 더 신중히 고민해봐야 될 문제인 것 같다.
(3) 부모객체와 매핑되지 않은 자식 객체의 삭제
만약 똑같은 상황에서 parent.addChild(child);를 실행하지 않은 자식 객체가 있다면 어떨까?
이 자식 객체의 경우 부모와 전혀 매핑이 안되어 있기때문에 em.remove(child) 코드도 실행이되어 삭제가 되고 부모 테이블로부터 독립된 상태로 자식 테이블에 존재한다.
고아객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아 객체라고 한다. 위에 (2)에서 다루었듯이 부모의 리스트에서 자식 객체를 삭제했으면 자식 객체도 자동으로 삭제할 수 있는 방법이 존재한다.
사용방법
@OneToMany
@OneToOne
@ManyToOne
어노테이션 등에 속성 orphanRemoval = true 으로 설정하면 된다.
Parent
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
// ... 생략
}
orphanRemoval은 false가 default값이고, true로 변경할 경우 부모 리스트에서 제거된 자식 객체의 엔티티 데이터는 자동 삭제된다.
- 당연하게도 부모 객체가 제거되면 자식도 함께 제거된다. (CascadeType.REMOVE 처럼 동작한다)
- 꼭 참조하는 곳이 하나일 때 사용해야한다. (완전 종속)
- 꼭 특정 엔티티가 개인 소유할 때 사용해야한다. (완전 종속)
+ 더나아가 CascadeType.ALL + orphanRemoval = true 이면 부모 엔티티를 통해 자식 생명주기를 전적으로 관리할 수 있다.
이 게시물은 '자바 ORM 표준 JPA 프로그래밍' 강의를 수강하고 정리한 내용임을 밝힙니다.
출처: https://www.inflearn.com/course/ORM-JPA-Basic#
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔
www.inflearn.com