공부/JPA

JPA 프록시와 연관관계 - 영속성 전이(CASCADE)와 고아 객체

데부한 2022. 7. 11. 21:52
반응형

출처 : 자바 ORM 표준 JPA 프로그래밍 인프런 강의

 

JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.

 

영속성 전이(CASCADE)와 고아 객체

영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때

  • 부모가 em.persist()를 날리면 자식까지 모두 자동으로 함께 저장되는 기능
  • 부모와 연관된 자식들을 한 번에 영속성 컨텍스트에 저장하고 싶은 경우 사용한다.

 

예제

Parent.class

@Entity
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

    public void addChild(Child child) {
        childList.add(child);
        child.setParent(this);
    }
    
    ... 생략
}

Child.class

@Entity
public class Child {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name="parent_id")
    private Parent parent;
    
    ... 생략
}

 

실행 클래스

Child child1 = new Child();
Child child2 = new Child();

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

em.persist(parent);
em.persist(child1);
em.persist(child2);

위의 코드는 Parent 객체를 중심으로 코드가 작성됐다. 그런데 객체들을 모두 영속성 컨텍스트에 올리려다 보니 Child 덕분에 배보다 배꼽이 더 커져버린 경우가 되어버렸다. 이런 귀찮음을 해결해 줄 수 있는 게 CASCADE이다.

 

CASCADE 적용

// Parent.class 중
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> childList = new ArrayList<>();

Parent.class에 연관 매핑 뒷부분에 cascade 옵션을 달아주면 적용은 끝이다. 실행 클래스에서 em.persist(ChildX); 코드 부분을 삭제해주어야 한다.

// 출력 로그

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를 설정하고 애플리케이션을 실행시켜보면 em.persist()는 하나인데 총 세 개의 insert 쿼리가 전송된다. 즉 두 개의 자식도 함께 자동으로 전달되는 것을 볼 수 있다.

CASCADE 종류

  • ALL : 모두 적용
  • PERSIST : 영속
  • REMOVE : 삭제
  • MERGE : 병합
  • REFRESH : REFREESH
  • DETACH : SETACH

 

주의사항!

  • 영속성 전이는 연관관계를 매핑하는 것과 아무런 관련이 없다.
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함 제공
  • 하나의 부모(소유자가 하나)가 자식을 관리할 때는 의미가 있는 기능이다.
  • 관계가 복잡하게 부모, 자식 외 다른 엔티티와 얽혀있는 경우 사용하지 않는 것이 좋다.

 


 

고아 객체

  • 고아 객체 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티
  • 고아 객체 제거 : 고아 객체(자식 엔티티)를 자동으로 삭제

 

예제

//Parent.class 중 일부
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();

orphanRemoval = true 속성을 주면 고아 객체 제거 기능이 활성화 된다.

실행 클래스

Child child1 = new Child();
Child child2 = new Child();

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

em.persist(parent);

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

Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0); // 자식 제거


// 출력 로그
Hibernate: 
    /* delete hellojpa.Child */ delete 
        from
            Child 
        where
            id=?

출력 로그에 보면 DELETE 쿼리를 날린 걸 볼 수 있다.

 

주의 사항!

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.
  • 참조하는 곳이 하나일 때 사용해야 한다.
  • 특정 엔티티가 개인 소유할 때 사용해야 한다.
  • @OneToOne, @OneToMany에만 사용이 가능하다.
  • 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화하면 부모를 제거할 때 자식도 함께 제거된다. CASCADEType.REMOVE와 같은 동작이다.

예제

// Parent.class
// cascade 주석처리
@OneToMany(mappedBy = "parent", /*cascade = CascadeType.ALL,*/ orphanRemoval = true)
private List<Child> childList = new ArrayList<>();

// 실행 클래스
Child child1 = new Child();
Child child2 = new Child();

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

em.persist(parent);
em.persist(child1);
em.persist(child2);

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

Parent findParent = em.find(Parent.class, parent.getId());
//findParent.getChildList().remove(0);
em.remove(findParent);


// 출력 로그
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=?

위의 코드에서 부모를 삭제했더니 Child에 대한 DELETE 쿼리도 발생해서 총 3개의 DELETE문이 날아간다.

 


 

영속성 전이 + 고아 객체, 생명 주기

  • CascadeType.ALL + orphanRemovel=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
    • 라이프 사이클을 JPA의 영속성 컨텍스트를 통해서 관리함
  • 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있다.
    • Child의 라이프 사이클을 Parent로 관리할 수 있다.
    • DB로 비유했을 때 DAO, Repository가 없어도 된다는 의미다.
  • 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하다.
반응형