영속성 전이: CASCADE
영속성 전이를 학습하는데 있어 해당 지식이 연관 관계, 지연로딩, 즉시로딩이랑 관련이 있다는 착각을 하는데
전~혀 관련 없는 지식이다.
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용.
ex: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.
영속성 전이: 저장
- 영속성 전이가 안되는 기본적인 엔티티 저장 방법
/*영속성 전이가 안되는 엔티티 저장 방법*/
...
@Entity
public class Parent{
...
@OneToMany(mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
...
}
@Entity
public class Child{
...
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
...
}
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);// persist를 3번이나 해야한다.
위와 같이 persist를 세 번 호출해야 정상적으로 동작을 한다. 뭔가 찝찝하고 귀찮은? 방식이다.
원하는 바는 코드 작성을 Parent를 중심으로 작성하고 싶어서 Child와 관련된 내용이 없었으면 좋겠다.
그래서 Parent가 Child를 관리해줬으면 하는 바램이 있다.
그렇다고 child를 persist하는 코드를 빼버린다면??
Parent만 persist되고 Child는 persist 되지 않는다!!!
그렇다면 Perent를 persist 단 한 번할 때 child 까지 같이 persist는 불가능한가?
- 영속성 전이(CASCADE)를 이용한 엔티티 저장 방법
/*영속성 전이가 안되는 엔티티 저장 방빕*/
...
@Entity
public class Parent{
...
@OneToMany(mappedBy = "parent", cascade=CascadeType.ALL)//영속성 전이 속성(CASCADE)사용
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
...
}
@Entity
public class Child{
...
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
...
}
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);// parent만 persist 해주니 child도 같이 persist된다.
parent만 persist해주니 그에 관련된 childList들도 같이 persist가 된다!!
주의!
- 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함 을 제공할 뿐
CASCADE의 종류
- ALL: 모두 적용(모든 곳에서 맞춰야 하면 해당 옵션)
- PERSIST: 영속(저장할 때만 사용 할 것이면 해당 옵션)
- REMOVE: 삭제
- MERGE: 병합
- REFRESH: REFRESH
- DETACH: DETACH
주로 ALL, PERSIST를 사용한다.
※ 영속성 전이(CASCADE)는 언제 써야 할까?
하나의 부모가 자식을 관리할 때 의미가 있다.
- ex. 게시판의 첨부 파일의 경로가 들어갈 때 사용된다 왜냐하면 첨부 파일 경로 관리는 한 게시물에서면 관리되기 때문이다. 그러나 파일을 여러 군대에서 관리한다면 사용하면 안된다.
즉, 전이 될 대상이 한 군데에서만 사용된다면 써도 된다.
하지만, 해당 엔티티(Child)가 특정 엔티티(Parent)에 종속되지 않고 여러군데서 사용된다면 사용하지 않는게 좋다.
- 라이프 사이클이 동일하기 때문에 단일 엔티티테 완전히 종속적일 때
- 단일 소유자 관계일 때
고아 객체
고아 객체 제거
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제한다.
- orphanRemoval = true
@Entity
public class Parent{
...
@OneToMany(mappedBy = "parent", cascade=CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child){
childList.add(child);
child.setParent(this);
}
...
}
@Entity
public class Child{
...
@ManyToOne
@JoinColumn(name = "parent_id")
private Parent parent;
...
}
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);// parent만 persist 해주니 child도 같이 persist된다.
em.flush();
em.clear();
Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0); // orphanRemoval 동작
연관 관계가 맺어진 Parent와 Child에서 Parent의 ChildList에 있는 0번째 Child를 remove() 하니깐 DB에 Delete 쿼리가 날라가는 것을 확인할 수 있다.
고아 객체 - 주의
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
- 참조하는 곳이 하나일 때 사용해야함.
- ex. 게시판의 첨부파일 경로를 삭제할 때
- 즉, 특정 엔티티가 개인 소유할 때 사용!!
- @OneToOne, @OneToMany만 가능
※ 참고
개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면,부모를 제거할 때 자식도 함께 제거된다. 마치 CascadeType.REMOVE, CascadeType.ALL처럼 동작한다.
- Parent객체를 지우게 되면 Parent가 소유하고 있는 ChildList에 속한 엔티티들이 전부 같이 삭제된다.
- CascadeType.ALL일 경우 Parent를 지우면 Delete가 자식 엔티티까지 전파되기 때문에 전부 삭제된다.
=== Parent ===
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
...
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
em.persist(child1);
em.persist(child2);
em.flush();
em.clear();
Parent findParent = em.find(Parent.class, parent.getId());
em.remove(findParent); // 부모 엔티티 삭제
영속성 전이 + 고아 객체, 생명주기
(CascadeType.ALL + orphanRemoval=true)
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다.
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
----
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();
Parent findParent = em.find(Parent.class, parent.getId());
em.remove(findParent);
findParent.getChildList().remove(0);
Parent 엔티티는 영속성 컨텍스트를 통해 직접 persist(), remove()를 하여 생명주기를 관리하고 있다. 그런데 두 옵션을 모두 활성화하면 Parent가 자식 엔티티의 생명주기까지 관리한다는 것이다.
※ 참고
Aggregate Root개념을 알기 위해 우선 Aggregate를 설명해야 한다.
- 연관 깊은 도메인들을 각각이 아닌 하나의 집합으로 다루는 것을 Aggregate라 한다. 즉 데이터 변경의 단위로 다루는 연관 객체의 묶음.
- Aggregate에는 루트(root)와 경계(boundary)가 있는데, 경계는 Aggregate에 무엇이 포함되고 포함되지 않는지를 정의한다.
루트는 단 하나만 존재하며, Aggregate에 포함된 특정 엔티티를 가르킨다.
경계안의 객체는 서로 참조가 가능하지만, 경계밖의 객체는 해당 Aggregate의 구성요소 가운데 루트만 참조할 수 있다.
※ 참고
DDD에서 루트 엔티티는 전역 식별성 Global Identity을 가진 엔티티라고 합니다.
Ex) 주문시스템의 Order Entity
실전 예제5 - 연관관계 관리
글로벌 페치 전략 설정
- 모든 연관관계를 지연 로딩으로
- @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 지연 로딩으로 변경
영속성 전이 설정
- Order → Delivery 를 영속성 전이 ALL로 설정
- 주문 생성 시 배송 정보도 같이 생성하며 생명주기를 맞추겠다.
- Order → OrderItem 을 영속성 전이 ALL로 설정
- 주문을 할때 주문 아이템도 같이 생성하여 생명주기 관리
- 주문만 persist를 해도 주문 아이템도 자동으로 같이 persist된다.
참고