JPA - 영속성 전이와 고아객체
우리는 JPA를 사용해서 엔티티를 설계할 때, 보통 연관관계를 맺어서 설계하는 경우가 대다수이다. 연관관계는 보통 1:N, N:1의 연관관계가 가장 많이 쓰일 것이다.
팀 엔티티와 회원 엔티티가 일대다 관계로 존재한다고 가정해보자. A팀을 데이터베이스에서 가져와 영속화 상태로 만들고, A팀 객체에 회원들을 추가시키려면 회원 객체도 영속화 상태로 만들어서 추가해야 정상적으로 데이터베이스 테이블에 반영될 것이다. 그런데 추가해야할 회원이 100명 또는 엄청 많은 회원이라면 모두 영속화 시켜야 할 것이다. 이 때, 영속성 전이 속성을 이용하면 이런 문제를 모두 해결해준다.
영속성 전이 - CASCADE
영속성 전이를 사용하는 목적은 다음과 같다.
- 특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을경우
- Ex) 부모 엔티티를 저장하면서 자식 엔티티도 함께 저장한다.
영속성 전이를 사용할 때 보통 @OneToMany, @OneToOne 의 관계에서 사용한다. 따라서 사용하는 방법은 다음과 같이 애노테이션의 속성에 추가해주기만 하면 된다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
이렇게 영속성 전이 속성을 부모 엔티티에 추가하면, 다음과 같이 부모 엔티티만 영속화해도 자식 엔티티도 같이 영속화되는 것을 볼 수 있다.
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);
INSERT 쿼리가 3번 발생한다.
Hibernate:
/* insert hellojpa.Parent
*/ insert
into
Parent
(name, PARENT_ID)
values
(?, ?)
Hibernate:
/* insert hellojpa.Child
*/ insert
into
Child
(name, PARENT_ID, CHILD_ID)
values
(?, ?, ?)
Hibernate:
/* insert hellojpa.Child
*/ insert
into
Child
(name, PARENT_ID, CHILD_ID)
values
(?, ?, ?)
CascadeType 의 종류
- ALL : 모두 적용한다. 즉, 엔티티를 영속, 비영속, 병합 등 할 때 연관된 엔티티도 같이 적용한다.
- PERSIST : 영속할 때만 적용한다.
- REMOVE : 삭제할 때만 적용한다.
- MERGE : 병합할 떄만 적용한다.
- REFRESH : REFRESH
- DETACH : 준영속 할 때만 적용한다.
영속성 전이를 사용할 때 알아두어야 할 점은 연관관계를 매핑하는 것과는 아무 상관없는 점이라는 것이다. 단지 엔티티를 영속화할 때 편리성을 위해서 연관된 엔티티도 함께 영속화 해주는 것 뿐이다.
고아 객체
고아 객체는 제거할 때 많이 쓰이는데, 고아 객체 제거란 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 것을 의미한다.
고아객체 제거도 @OneToMany, @OneToOne 의 연관관계에서 사용하는데 다음과 같은 속성을 달아주기만 하면 된다.
@OneToMany(mappedBy = "parent", orphanRemoval = true)
이렇게 보면 영속성 전이의 REMOVE 속성과 비슷한 느낌이 들 것이다. 사실 개념적으로 부모를 제거하면 자식도 고아가 되기 때문에 고아객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다. 따라서 이 개념은 CascadeType.REMOVE 처럼 동작한다.
또한 이런 경우가 있을 수 있다. 부모 엔티티에서 참조하고 있는 자식 컬렉션에서 자식을 한명 삭제할 수 있을 것이다. 이런 경우에 자식을 컬렉션에서 삭제할 뿐만아니라 데이터베이스 테이블 내에서도 삭제가 되는 것이다. 고아객체제거 기능을 사용하는 이유는 바로 이런 경우 때문이다.
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
위의 소스코드로 인해 데이터베이스로 나가는 쿼리는 다음과 같다.
Hibernate:
select
parent0_.PARENT_ID as parent_i1_5_0_,
parent0_.name as name2_5_0_
from
Parent parent0_
where
parent0_.PARENT_ID=?
Hibernate:
select
children0_.PARENT_ID as parent_i3_1_0_,
children0_.CHILD_ID as child_id1_1_0_,
children0_.CHILD_ID as child_id1_1_1_,
children0_.name as name2_1_1_,
children0_.PARENT_ID as parent_i3_1_1_
from
Child children0_
where
children0_.PARENT_ID=?
Hibernate:
/* delete hellojpa.Child */ delete
from
Child
where
CHILD_ID=?
고아 객체 - 주의할 점
고아 객체를 사용하면 마냥 다 좋은 것 같지만, 주의할점이 존재한다.
- 고아 객체 제거는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.
- 따라서 반드시 참조하는 곳이 하나일 때 사용해야 한다.
- 즉, 특정 엔티티가 개인소유할 때 사용해야 한다.
- @OneToOne, @OneToMany 에서만 사용가능하다.
영속성 전이 + 고아 객체
위에서 설명한 영속성 전이와 고아 객체를 동시에 사용할 수도 있는데, 동시에 사용하면 나타나는 특징은 다음과 같다.
- CascadeType.ALL + orphanRemovel = true
- 두 옵션을 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다. 즉, 자식 엔티티를 위한 DAO를 만들지 않아도 된다.
- 따라서 자식은 em.persist(), em.remove()와 같은 메서드로 스스로 생명주기를 관리하지 않아도 된다.
- 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용하다.
'JPA' 카테고리의 다른 글
JPA - 프록시와 즉시, 지연로딩 (0) | 2022.06.06 |
---|---|
JPA 영속성 컨텍스트 - 1차캐시와 변경감지 (0) | 2022.05.31 |
연관관계 매핑 종류 (0) | 2022.05.21 |
값 타입 (0) | 2022.02.02 |
프록시와 연관관계 관리 (0) | 2022.02.02 |