영속성 전이: CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 됩니다. 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에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태어야 합니다.
따라서 위의 코드를 보면 부모 엔티티를 영속 상태로 만들고 자식 엔티티도 각각 영속 상태로 만듭니다. 이럴 때 영속성 전이를 사용하면 부모 엔티티만 영속 상태로 만들면 연관된 자식까지 한번에 영속 상태로 만들 수 있습니다.

영속성 전이: 저장

영속성 전이를 활성화하는 CASCADE 옵션을 적용해보겠습니다.

@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);
}

CASCADE 실행

스크린샷 2019-11-23 오전 2 52 25

부모만 영속화하면 CascadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장합니다.

이 코드의 쿼리 결과를 보면 데이터가 정상적으로 2건 입력된 것을 확인할 수 가 있습니다.

스크린샷 2019-11-23 오전 2 54 54

영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없습니다. 단지 엔티티를 영속화 할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐입니다.

영속성 전이: 삭제

방금 저장한 부모와 자식 엔티티를 모두 제거하려면 다음 코드와 같이 각각의 엔티티를 하나씩 제거해야 합니다.

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를 설정하지 않고 이 코드를 실행하면 부모 엔티티만 삭제 됩니다. 하지만 데이터베이스의 부모 로우를 삭제하는 순간 자식 테이블에 걸려 있는 외래 키 제약조건으로 인해, 데이터베이스에서 외래 키 무결성 예외가 발생합니다.

CASCADE의 종류

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); //자식 엔티티를 컬렉션에서 제거

실행결과

스크린샷 2019-11-23 오후 9 06 09

사용 코드를 보면 컬렉션에서 첫 번째 자식을 제거합니다. 고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용되므로 플러시 시점에 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 프로그래밍

'SpringFramework > JPA' 카테고리의 다른 글

JPA에 대한 사실과 오해  (0) 2019.11.28
값 타입  (0) 2019.11.26
지연로딩과 즉시로딩  (0) 2019.11.23
프록시와 연관관계 관리  (0) 2019.11.22
고급매핑 - 조인테이블  (0) 2019.10.30

+ Recent posts