본문 바로가기

Spring/JPA

JPA- Cascade와 고아 객체

Cascade

 cascade를 사용하면 엔티티를 persist할 때 연관된 엔티티도 같이 persist 된다.

 

Parent.class

@Entity
@Getter @Setter
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    // CascadeType.ALL하면 Parent를 persist할 때 리스트 내부에 있는 녀석들도 모두 persist됨
    @OneToMany(mappedBy = "parent",cascade = CascadeType.ALL)
    private List<Child> childList = new ArrayList<>();


    // 연관관계 편의 메소드
    void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }

}

Child.class

@Entity
@Getter @Setter
public class Child {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Parent parent;
}

JpaMain.class

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Child child1 = new Child();
            Child child2 = new Child();
            Parent parent = new Parent();

            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);  //parent만 persist 하여도 child1, child2도 persist된다.

            tx.commit();
        } catch (Exception e) {
            tx.rollback(); //오류 발생 시 롤백
        } finally {
            //종료
            em.close();
        }
        emf.close();
    }
}

결과

Hibernate: 
    /* insert hellojpa.Parent
        */ insert 
        into
            Parent
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Child
        */ insert 
        into
            Child
            (name, PARENT_ID, id) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert hellojpa.Child
        */ insert 
        into
            Child
            (name, PARENT_ID, id) 
        values
            (?, ?, ?)

 Cascade같은 경우 한 부모에 여러 child가 있는 경우에 주로 많이 쓴다. 주의해야 할 점은 Child를 Parent라는 하나의 엔티티만이 관리(연관)할 때 사용하여야 한다.

 

 예를 들어서 Child 엔티티가 Parent1 엔티티와 Parent2 엔티티 두개에 연관이 되어있는 경우 Child는 연관된 엔티티에서 Cascade 설정을 해서는 안된다. 연관된 엔티티가 하나일 때만 Cascade를 사용한다.

 

  Cascade 종류

  • ALL : 모두 적용
  • PERSIST : 연관된 엔티티가 persist될 때 같이 영속됨
  • REMOVE : 연관된 엔티티가 remove될 때 같이 삭제됨

고아 객체

 고아 객체부모 엔티티와 연관관계가 끊어진 자식 엔티티를 의미한다.

고아 객체 제거고아객체가 자동으로 삭제되는 것이다.

Parent.class

@Entity
@Getter @Setter
public class Parent {  // Parent와 Child는 일대다 양방향 매핑

    @Id @GeneratedValue
    private Long id;

    private String name;

    // orphanRemoval = true 로 설정하면 고아객체 자동 삭제 설정이 되어, childList에서 제거된 Child 엔티티는 CHILD 테이블에서 자동으로 삭제된다.
    @OneToMany(mappedBy = "parent", orphanRemoval = false)
    private List<Child> childList = new ArrayList<>();


    // 연관관계 편의 메소드
    void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }

}

JpaMain.class

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Parent parent = new Parent();
            Child child1 = new Child();
            Child child2 = new Child();
            child1.setName("1");
            child2.setName("2");

            parent.addChild(child1);
            parent.addChild(child2);
            
			// CascadeType.ALL: parent만 persist 하여도 child1, child2도 persist된다.
            em.persist(parent);
            em.persist(child1); 
            em.persist(child2); 

            em.flush();
            em.clear();

            Parent findParent = em.find(Parent.class, parent.getId());
            // childList의 0번째 인덱스 요소가 childList에서 제거됨.
            // orphanRemoval = true이므로 리스트에서 제거된 엔티티(= 0번째 child)는 child 테이블에서도 삭제된다.
            findParent.getChildList().remove(0);

            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            //종료
            em.close();
        }
        emf.close();
    }
}

JpaMain 실행결과 Child가 제거됨

Hibernate: 
    /* delete hellojpa.Child */ delete 
        from
            Child 
        where
            id=?

 

 

DB의 모습을 보면 다음과 같다.

 1) @OneToMany(orphanRemoval = false)인 경우 (디폴트)

childList에서 제거가 되었어도 여전히 CHILD 테이블에선 남아있는 모습.

2)@OneToMany(orphanRemoval = True)인 경우 (고아 객체 삭제 설정)

childList에서 제거된 1번 Child 엔티티는 CHILD 테이블에서도 삭제가 된 모습.

 

  고아 객체 사용시 주의할 점

엔티티가 테이블에서 제거된다는 점은 딱봐도 위험하다. 그러므로 몇가지 주의할 점이 존재한다.

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야한다!
  • 특정 엔티티가 개인 소유할 때만 사용
  • @OneToMany, @OneToOne만 가능 : 참조하는 쪽이 하나인 경우만 사용

 

 개념적으로 부모를 제거하면 자식은 고아가된다. 따라서 고아 객체 제거기능을 활성화하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.

em.remove(findParent);  // findParent 제거하면 연관된 chlid도 다 제거됨.
Hibernate: 
    /* delete hellojpa.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete hellojpa.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete hellojpa.Parent */ delete 
        from
            Parent 
        where
            id=?

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

JPA- 값 타입과 불변 객체  (0) 2023.03.23
JPA- 임베디드 타입  (0) 2023.03.22
JPA- 즉시 로딩, 지연 로딩  (0) 2023.03.16
JPA- 프록시  (0) 2023.03.16
JPA- @MappedSuperClass  (0) 2023.03.12