본 글은 인프런 김영한님의 JPA 로드맵을 기반으로 정리했습니다.
영속성 전이는 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용한다. 영속성 전이는 연관관계를 매핑하고 연관관계의 주인을 설정하는 것과 아무 관련이 없다. 단지 엔티티를 영속화할 때 연관된 엔티티를 함께 영속화하는 편의 기능을 제공하는 것이다.
@Entity
@Getter @Setter
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
}
@Entity
@Getter @Setter
public class Child {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PARENT_ID")
private Parent parent;
}
Parent와 Child는 1:N 양방향 연관관계를 가진다. 이 때 @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
을 통해 Parent의 영속성을 Child로 전파할 수 있다. 위의 경우 ALL로 설정했기 때문에 두 엔티티의 생명 주기가 동일해진다. Parent를 저장하면 연관된 Child도 함께 저장되고, Parent를 삭제해면 연관된 Child가 함께 삭제된다.
사용할 수 있는 CascadeType은 아래와 같다. ALL, PERSIST 외에는 자주 쓰이지 않는다.
ALL: 모두 적용
PERSIST: 영속
REMOVE: 삭제
MERGE: 병합
REFRESH: REFRESH
DETACH: DETACH
고아 객체 제거 기능은 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능이다.
코드로 표현하면 다음과 같다.
Parent parent = em.find(Parent.class, 1L);
parent.getChildren().remove(0);
Parent 엔티티로부터 Child 엔티티로의 참조를 제거했다. 고아 객체 제거 기능이 켜져있다면 영속성 컨텍스트가 플러시될 때 다음과 같은 SQL이 자동으로 데이터베이스에 날라간다.
DELETE FROM CHILD WHERE ID = xxx;
고아 객체 제거 기능은 이름에서 유추할 수 있듯이 자식 엔티티가 부모 엔티티에 종속되었을 때 사용하는 기능이다. 다른말로 표현하자면 부모로부터의 참조가 제거된 고아 엔티티는 다른 곳에서 참조하지 않는다고 판단하고 삭제하는 것이다. 그렇기 때문에 참조하는 곳이 하나일 때 사용해야한다. 특정 엔티티가 개인 소유할 때 사용해야한다. 여러 엔티티에서 참조하는 엔티티에는 고아 객체 제거 기능을 사용하면 안 된다.
예를 들어 게시물과 댓글을 생각해보자. 일반적인 경우 댓글은 게시물 외에 다른 엔티티에서 참조할 일이 없다. 이 때는 고아 객체 제거 기능을 사용해도된다.
고아 객체 제거 기능은 부모의 @OneToOne
, @OneToMany
에만 사용할 수 있다. 두 애노테이션을 뒤집어서 자식 입장에서 보면 @OneToOne
, @ManyToOne
이다. 즉, 고아 객체 제거 기능으로 삭제하려는 엔티티는 부모와 일대일이나 다대일 관계로 부모에 종속되어야한다.
자식과 부모는 개념적인 설명임에 주의하자. 특정 엔티티와 x대일 관계를 맺는다고 무조건 자식 엔티티가 되는 것은 아니다. 예를 들어, 주문의 경우 회원과 다대일 관계를 맺는다. 그러나 주문은 회원에게만 종속되지 않기 때문에 회원의 자식이 아니다. 주문은 통계나 정산 등 여러 엔티티에서 쓰이기 때문에 회원 엔티티의 컬렉션에서 참조를 제거한다고 해서 함부로 삭제하면 안 된다.
참고로 고아 객체 제거 기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다. 부모를 삭제하면 당연히 자식 엔티티의 컬렉션도 모두 삭제되기 때문에 마치 CascadeType.REMOVE
처럼 동작한다. 위의 예제에 적용한다면, 회원 엔티티를 삭제했을 때 회원이 주문했던 모든 주문 엔티티들이 함께 삭제되어 버린다. 이는 일반적으로 원하는 상황은 아닐 것이다.
고아 객체 제거 기능은 다음과 같이 활성화 한다.
@Entity
@Getter @Setter
public class Parent {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();
}
@OneToOne
이나 @OneToMany
의 orphanRemoval 속성을 true로 주면 된다. 기본값은 false다.
위의 코드처럼 CascadeType.ALL
과 orphanRemoval=true
를 함께 사용하면 자식 엔티티의 생명 주기를 부모 엔티티로 관리할 수 있다. 부모 엔티티는 em.persist()
, em.remove()
등을 통해 스스로 생명 주기를 관리하고 자식 엔티티의 생명 주기는 부모 엔티티에 의존하는 것이다. 부모 엔티티가 영속화되면 자식 엔티티도 함께 영속화되고 부모 엔티티가 삭제되면 자식 엔티티도 함께 삭제된다. 부모 엔티티로부터의 참조가 끊어진 자식 엔티티는 고아 객체로 판단하고 삭제된다.