저번에 잠깐 알아보긴 했는데 오늘 포스팅하는김에 한개 더 남기자

영속성 전이

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶다면 영속성 전이 기능을 사용하면 된다.
JPA의 CASCADE옵션으로 영속성 전이를 제공한다. 쉽게 말해서 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장할 수 있다.

@Entity
@Data
public class Parent {

  @Id @GeneratedValue
  private Long id;

  @OneToMany(mappedBy = "parent")
  private List<Child> children = new ArrayList<>();
}

@Entity
@Data
public class Child {

  @Id @GeneratedValue
  private Long ig;

  @ManyToOne
  private Parent parent;
}

이런 엔티티가 있다고 가장하자. 1대N의 구조인 부모와 자식관계를 나타내고 있다.
만약 1명의 부모와 2명의 자식을 저장한다고 가정할때 아래 코드와 같이 작성할 것이다.

private static void saveNoCascade(EntityManager entityManager){
  Parent parent = new Parent();
  entityManager.persist(parent);

  Child child1 = new Child();
  child1.setParent(parent);
  entityManager.persist(child1);

  Child child2 = new Child();
  child2.setParent(parent);
  entityManager.persist(child2);
}

JPA에서 엔티티를 저장할 때 연관된 엔티티는 모두 영속 상태이어야 한다. 따라서 부모 엔티티를 영속 상태로 만들고 나머지 두개의 자식들도 영속 상태로 만들었다.

영속성 전이 (저장)

@Entity
@Data
public class Parent {

  @Id @GeneratedValue
  private Long id;

  @OneToMany(mappedBy = "parent" ,cascade = CascadeType.PERSIST)
  private List<Child> children = new ArrayList<>();
}

위와 같이 cascade = CascadeType.PERSIST 로 지정하면 부모를 영속화할 때 연관된 자식들도 함께 영속화 한다.

private static void saveWithCascade(EntityManager entityManager){
  Child child1 = new Child();
  Child child2 = new Child();

  Parent parent = new Parent();
  child1.setParent(parent);
  child2.setParent(parent);

  parent.getChildren().add(child1);
  parent.getChildren().add(child2);

  entityManager.persist(parent);
}

부모 엔티티만 영속화를 했지만 CascadeType.PERSIST 같은 속성으로 자식 엔티티까지 함께 영속화해서 저장한다.

영속성 전이 (삭제)

부모와 자식 엔티티를 모두 제거하려면 아래와 같이 각각 엔티티를 한개씩 삭제해야 한다.

private static void deleteNoCascade(EntityManager entityManager){
  Parent parent = entityManager.find(Parent.class, 1L);
  Child child1 = entityManager.find(Child.class, 2L);
  Child child2 = entityManager.find(Child.class, 3L);

  entityManager.remove(child1);
  entityManager.remove(child2);
  entityManager.remove(parent);
}

영속성 전이 CascadeType.REMOVE를 사용하면 부모 엔티티와 연관된 자식 엔티티도 함께 삭제 된다.

@OneToMany(mappedBy = "parent" ,cascade = CascadeType.REMOVE)
private List<Child> children = new ArrayList<>();
private static void deleteWithCascade(EntityManager entityManager){
  Parent parent = entityManager.find(Parent.class, 1L);
  entityManager.remove(parent);
}

코드를 실행하면 DELETE SQL을 3번 실행하고 부모는 물론 연관된 자식도 모두 삭제한다. 삭제 순서를 외래키를 고려해서 자식부터 삭제하고 부모를 삭제한다. 만약 CascadeType.REMOVE를 설정 하지 않고 하면 어떻게 될까?
부모 엔티티만 삭제 하려다 외래키 제약 조건으로 인해 예외가 발생한다.

java.lang.IllegalArgumentException: attempt to create delete event with null entity

이외에도 CascadeType 속성은 여러가지가 있다 CascadeType enum을 통해 확인

고아 객체

JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체 제거라 한다. 이 기능을 사용해서 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제 된다.

@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();
private static void deleteOrphan(EntityManager entityManager){
  Parent parent = entityManager.find(Parent.class, 1L);
  parent.getChildren().remove(0);
}

이렇게 하면 컬렉션에서 첫번째 자식을 삭제한다고 되어있는데 나는 왜 안되지? orphanRemoval = true 을 주면 데이터베이스에서도 삭제가 된다고 하는데..
만약 모두 비우고 싶다면 clear() 메서드를 사용하면 된다. orphanRemoval 옵션은 @OneToOne 또는 @OneToMany에서만 사용할 수 있다.

영속성 전이 + 고아 객체

orphanRemoval = true 와 CascadeType.ALL을 동시에 사용하면 어떻게 될까? 일반적으로 엔티티는 entityManager.persist() 통해 영속화 되고 entityManager.remove() 를 통해 제거 된다. 이것은 엔티티 스스로 생명주기를 관리한다는 뜻이다. 그런데 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기까지 관리할 수 있다.
자식을 저장하려면 부모에 등록만 하면 된다. (CASCADE)

Parent parent = entityManager.find(Parent.class, parentId);
parent.addChild(child1);

자식을 삭제하려면 부모에서 제거하면 된다.

Parent parent = entityManager.find(Parent.class, parentId);
parent.getChildren().remove(child1);

근데 왜 나는 orphanRemoval = true 이게 안먹힐까?