본문 바로가기

Spring/JPA

orphanRemoval (고아 객체)

orphanRemoval

고아 객체... 번역이 조금... 그렇지만 말 그대로 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아 객체라 부릅니다. 여기서 부모 엔티티와 자식 엔티티는 어떤 기준으로 나눌까요? 

 

엔티티 간의 관계 설정에서 부모-자식 관계의 기준 정립은 데이터의 구조와 비즈니스 로직에 따라 달라질 수 있습니다. 대체로, 연관관계의 방향을 정할 때, 단일 엔티티(부모)가 컬렉션 타입의 여러 엔티티(자식)를 참조하는 구조에서는 ‘일대다’ 관계가 형성되며, 이때 ‘다’ 쪽에 해당하는 엔티티가 자식 엔티티가 됩니다. 연관관계의 주인과 부모-자식 관계가 혼돈될 수 있으니 주의해야 합니다.

 

자바 JVM에 익숙해서 참조가 끊긴 객체를 가비지 컬렉터가 자동으로 삭제하는 과정처럼 JPA에서도 부모 엔티티와 연관관계가 끊긴다면 자식 엔티티도 당연히 삭제된다고 생각할 수 있습니다.

 

하지만 JPA에서는 orphanRemoval을 true로 설정해야 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제할 수 있습니다. 아래 코드와 같이 부모 엔티티와 연관관계가 끊긴 자식 엔티티를 orphanRemoval을 통해 자동으로 DB에서 삭제합니다.

 

또한 부모 엔티티가 제거되면 자식 엔티티는 고아 객체가 되며 orphanRemoval true 경우, 부모 엔티티를 제거할 자식 엔티티도 함께 제거됩니다. 이것은 CascadeType.REMOVE처럼 동작합니다.

Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거

 

참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제합니다. 따라서 orphanRemoval 설정도 cascade 설정과 마찬가지로 참조하는 곳이 하나일 때만 사용해야 합니다. 다시 말해 특정 엔티티가 개인 소유할 때만 orphanRemoval을 true로 설정해야 합니다.

 

그렇지 않다면, 위의 코드처럼 parent1의 children 컬렉션에서 0번째 child의 참조를 제거하여 0번째 child를 고아 객체로 인식하고 자동으로 삭제했지만 실제로는 애플리케이션 다른 곳에서 0번째 child를 참조하고 있다면 문제가 생깁니다.

 

또한 orphanRemoval 설정은  @OneToOne @OneToMany 애노테이션에서만 사용 가능합니다.

// @OneToOne(mappedBy = "parent", orphanRemoval = true)
// @OneToMany(mappedBy = "parent", orphanRemoval = true)
public class Team {

    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    @Column(name = "NAME")
    private String name;

    @OneToMany(
            mappedBy = "team",
            orphanRemoval = true,
            cascade = CascadeType.PERSIST
    )
    private List<Member> members = new ArrayList<>();

    // custructor

}

 

orphanRemoval을 정리하면, 부모 엔티티가 삭제되면 자식 엔티티도 삭제됩니다. 다시 말해 부모가 자식의 삭제 생명 주기를 관리합니다. 또한 부모 엔티티와 자식 엔티티 사이의 연관관계를 제거하면, 자식 엔티티는 고아 객체로 취급되어 DB에서 삭제됩니다.

 

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

em.persist()와 em.remove()을 사용해서 영속성 컨텍스트를 직접적으로 통해서 엔티티의 생명주기를 관리할 수 있지만, CascadeType.ALL과 orphanRemoval = true를 동시에 설정하여 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있습니다. 이와 같은 생명주기 관리는 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용합니다. 

 

참고로 CascadeType.All (PERSIST + REMOVE) 사용하면 orphanRemoval 속성 없이도 자식 엔티티가 부모 엔티티의 생명주기를 따라가기 때문에 REMOVE 고아 객체가 삭제된다고 오해할 있습니다. 하지만  CascadeType.All 만으로는 엔티티 간의 관계 단절 자식 엔티티를 삭제하지 않습니다. 반면 orphanRemoval = true 옵션은 관계 단절 자식 엔티티를 삭제합니다.

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

CASCADE (영속성 전이)  (2) 2024.10.21
즉시 로딩과 지연 로딩  (0) 2024.10.21
프록시 (Proxy)  (0) 2024.10.21
@MappedSuperclass  (0) 2024.10.17
상속관계 매핑  (2) 2024.10.17