특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 됩니다. JPA는 CASCADE 옵션
으로 영속성 전이를 제공합니다. 쉽게 말해서 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장 할 수 있습니다.
@Setter
@Getter
@Entity
public class Parent {
@Id
@GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<Child>();
}
@Getter
@Setter
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne
private Parent parent;
}
위의 코드는 부모 엔티티가 여러 자식 엔티티를 가진다고 가정해보겠습니다.
@Test
@Transactional
public void printUser() throws Exception {
// 부모 저장
Parent parent = new Parent();
parent.setName("임종수");
entityManager.persist(parent);
// 1번 자식 저장
Child child1 = new Child();
child1.setName("임준영");
child1.setParent(parent); // 자식 -> 부모 연관관계 설정
parent.getChildren().add(child1); // 부모 -> 자식
entityManager.persist(child1);
// 2번 자식 저장
Child child2 = new Child();
child2.setName("임주리");
child2.setParent(parent); // 자식 -> 부모 연관관계 설정
parent.getChildren().add(child2); // 부모 -> 자식
entityManager.persist(child2);
}
JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태어야 합니다.
따라서 위의 코드를 보면 부모 엔티티를 영속 상태로 만들고 자식 엔티티도 각각 영속 상태로 만듭니다. 이럴 때 영속성 전이를 사용하면 부모 엔티티만 영속 상태로 만들면 연관된 자식까지 한번에 영속 상태로 만들 수 있습니다.
@Setter
@Getter
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
}
부모를 영속화 할 때 자식들도 함께 영속화하라고 cascade = CascadeType.PERSIST
옵션을 설정했습니다. 이 옵션을 적용하면 아래 코드처럼 간편하게 부모와 자식 엔티티를 한 번에 영속화 할 수 있습니다.
@Test
@Transactional
@Rollback(false)
public void printUser() throws Exception {
// 1번 자식 저장
Child child1 = new Child();
// 2번 자식 저장
Child child2 = new Child();
Parent parent = new Parent();
parent.setName("임종수");
child1.setName("임준영");
child2.setName("임주리");
child1.setParent(parent); // 자식 -> 부모 연관관계 설정
child2.setParent(parent); // 자식 -> 부모 연관관계 설정
parent.getChildren().add(child1); // 부모 -> 자식
parent.getChildren().add(child2); // 부모 -> 자식
// 부모 저장
entityManager.persist(parent);
}
부모만 영속화하면 CascadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장합니다.
이 코드의 쿼리 결과를 보면 데이터가 정상적으로 2건 입력된 것을 확인할 수 가 있습니다.
영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없습니다. 단지 엔티티를 영속화 할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐입니다.
방금 저장한 부모와 자식 엔티티를 모두 제거하려면 다음 코드와 같이 각각의 엔티티를 하나씩 제거해야 합니다.
Parent findParent = em.find(Parent.class, 1L);
Child findChild1 = em.find(Child.class, 1L);
Child findChild2 = em.find(Child.class, 2L);
em.remove(findChild1);
em.remove(findChild2);
em.remove(findParent);
영속성 전이는 엔티티를 삭제할 때도 사용할 수 있습니다. CascadeType.REMOVE로 설정하고 다음 코드처럼 부모 엔티티만 삭제하면 연관된 자식 엔티티도 함께 삭제 됩니다.
Parent findParent = em.find(Parent.class, 1L);
em.remove(findParent);
코드를 실행하면 DELETE SQL을 3번
실행하고 부모는 물론 연관된 자식도 모두 삭제합니다. 삭제 순서는 외래키 제약조건을 고려해서 자식을 먼저 삭제하고 부모를 삭제합니다.
만약 CascadeType.REMOVE를 설정하지 않고 이 코드를 실행하면 부모 엔티티만 삭제 됩니다. 하지만 데이터베이스의 부모 로우를 삭제하는 순간 자식 테이블에 걸려 있는 외래 키 제약조건으로 인해, 데이터베이스에서 외래 키 무결성 예외
가 발생합니다.
public enum CascadeType{
ALL, // 모두적용
PERSIST, // 영속
MERGE, // 병합
REMOVE, // 삭제
REFRESH, // REFRESH
DETACH // DETACH
}
다음처럼 여러 속성을 같이 사용할 수 있습니다.
cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
참고로 CascadeType.PERSIST, CascadeType.REMOVE는 em.persist(), em.remove()를 실행 할 때 바로 전이가 발생하지 않고 플러시
를 호출 할 때 전이
가 발생합니다.
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체 제거라고 합니다.
이 기능을 사용해서 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제하도록 코드를 작성해 보겠습니다.
@Setter
@Getter
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
}
고아 객체 제거 기능을 활성화하기 위해 컬렉션에 orphanRemoval = true
를 설정합니다. 이제 컬렉션에서 제거한 엔티티는 자동으로 삭제됩니다.
Parent parent1 = em.find(Parent.lcass, id);
parent1.getChildren().remove(0); //자식 엔티티를 컬렉션에서 제거
사용 코드를 보면 컬렉션에서 첫 번째 자식을 제거합니다. 고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용되므로 플러시 시점에 DELETE SQL이 실행됩니다.
모든 자식 엔티티를 제거하려면 다음 코드처럼 컬렉션을 비우면 됩니다.
parent1.getChildren().clear();
고아 객체를 정리하면 참조가 제거된 엔티티는 다른곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능입니다. 따라서 이 기능은 참조하는 곳이 하나일 때만 사용해야 합니다. 쉽게 말하자면 특정 엔티티가 개인 소유하는 엔티티
에만 이 기능을 적용해야 합니다. 만약 삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있습니다. 이런 이유로 orphanRemovel은 @OneToOne, @OneToMany
에만 사용할 수 있습니다.
CascadeType.ALL + orphanRemoval = true
를 동시에 사용하면 부모 엔티티를 통해서 자식의 생명주기를 관리 할 수 있습니다.
//자식을 저장하려면 부모에 등록만 하면 됩니다.
Parent parent = em.find(Parent.class , parentId);
parent.addChild(child);
//자식을 삭제하려면 부모에서 제거하면 됩니다.
Parent parent = em.find(Parent.class , parentId);
parent.getChildren().remove(removeObject);
참조 문헌: ORM 표준 JPA 프로그래밍