반응형
JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.
영속성 전이(CASCADE)와 고아 객체
영속성 전이 : CASCADE
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
- 부모가 em.persist()를 날리면 자식까지 모두 자동으로 함께 저장되는 기능
- 부모와 연관된 자식들을 한 번에 영속성 컨텍스트에 저장하고 싶은 경우 사용한다.
예제
Parent.class
@Entity
public class Parent {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {
childList.add(child);
child.setParent(this);
}
... 생략
}
Child.class
@Entity
public class Child {
@Id
@GeneratedValue
private Long id;
private String name;
@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);
위의 코드는 Parent 객체를 중심으로 코드가 작성됐다. 그런데 객체들을 모두 영속성 컨텍스트에 올리려다 보니 Child 덕분에 배보다 배꼽이 더 커져버린 경우가 되어버렸다. 이런 귀찮음을 해결해 줄 수 있는 게 CASCADE이다.
CASCADE 적용
// Parent.class 중
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();
Parent.class에 연관 매핑 뒷부분에 cascade 옵션을 달아주면 적용은 끝이다. 실행 클래스에서 em.persist(ChildX); 코드 부분을 삭제해주어야 한다.
// 출력 로그
Hibernate:
/* insert hellojpa.Parent
*/ insert
into
Parent
(name, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Child
*/ insert
into
Child
(name, parent_id, id)
values
(?, ?, ?)
Hibernate:
/* insert hellojpa.Child
*/ insert
into
Child
(name, parent_id, id)
values
(?, ?, ?)
cascade를 설정하고 애플리케이션을 실행시켜보면 em.persist()는 하나인데 총 세 개의 insert 쿼리가 전송된다. 즉 두 개의 자식도 함께 자동으로 전달되는 것을 볼 수 있다.
CASCADE 종류
- ALL : 모두 적용
- PERSIST : 영속
- REMOVE : 삭제
- MERGE : 병합
- REFRESH : REFREESH
- DETACH : SETACH
주의사항!
- 영속성 전이는 연관관계를 매핑하는 것과 아무런 관련이 없다.
- 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함 제공
- 하나의 부모(소유자가 하나)가 자식을 관리할 때는 의미가 있는 기능이다.
- 관계가 복잡하게 부모, 자식 외 다른 엔티티와 얽혀있는 경우 사용하지 않는 것이 좋다.
고아 객체
- 고아 객체 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티
- 고아 객체 제거 : 고아 객체(자식 엔티티)를 자동으로 삭제
예제
//Parent.class 중 일부
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
orphanRemoval = true 속성을 주면 고아 객체 제거 기능이 활성화 된다.
실행 클래스
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());
findParent.getChildList().remove(0); // 자식 제거
// 출력 로그
Hibernate:
/* delete hellojpa.Child */ delete
from
Child
where
id=?
출력 로그에 보면 DELETE 쿼리를 날린 걸 볼 수 있다.
주의 사항!
- 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.
- 참조하는 곳이 하나일 때 사용해야 한다.
- 특정 엔티티가 개인 소유할 때 사용해야 한다.
- @OneToOne, @OneToMany에만 사용이 가능하다.
- 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면 부모를 제거할 때 자식도 함께 제거된다. CASCADEType.REMOVE와 같은 동작이다.
예제
// Parent.class
// cascade 주석처리
@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.persist(child1);
em.persist(child2);
em.flush();
em.clear();
Parent findParent = em.find(Parent.class, parent.getId());
//findParent.getChildList().remove(0);
em.remove(findParent);
// 출력 로그
Hibernate:
/* delete hellojpa.Child */ delete
from
Child
where
id=?
Hibernate:
/* delete hellojpa.Child */ delete
from
Child
where
id=?
Hibernate:
/* delete hellojpa.Parent */ delete
from
Parent
where
id=?
위의 코드에서 부모를 삭제했더니 Child에 대한 DELETE 쿼리도 발생해서 총 3개의 DELETE문이 날아간다.
영속성 전이 + 고아 객체, 생명 주기
- CascadeType.ALL + orphanRemovel=true
- 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
- 라이프 사이클을 JPA의 영속성 컨텍스트를 통해서 관리함
- 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다.
- Child의 라이프 사이클을 Parent로 관리할 수 있다.
- DB로 비유했을 때 DAO, Repository가 없어도 된다는 의미다.
- 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하다.
반응형