DEV ℧ Developer Diary

[JPA] A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance

이슈 사항

발생 예외

Caused by: org.springframework.orm.jpa.JpaSystemException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: kr.co.api.product.domain.classification.ProductClassification.metaHistories; nested exception is org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance

productRepository.save(products) 와 같이 새로운 정보를 insert 하려 할 시 위와 같은 예외처리가 발생

소스

@Setter
@OneToMany(mappedBy = "productOrder", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Product> products;

...

if (CollectionUtils.isEmpty(this.products)) {
    products = new ArrayList<>(tempProducts);
} else {
    products.addAll(tempProducts);
}

발생 원인

해당 소스에서 위와 같은 예외가 발생한 이유는 기존 자식 컬렉션인 products를 초기화 하여 새로운 데이터를 넣어주기 위해 new ArrayList<>()를 통해 기존의 인스턴스를 날리고 새로운 참조를 부여하는 과정에서 발생한 이슈입니다.

기존의 products에서 새로운 참조를 부여함에 따라 자바 컬렉션의 관점에서는 초기화가 되었습니다. 하지만 JPA의 관점에서는 살짝 다르게 작용합니다. JPA에서는 products의 컬렉션과 해당 Entity와 연결이 해제되었지만. 세션(영속성 컨텍스트)에는 남아있게 되어 해당 예외가 발생하게 됩니다.

해당 이슈는 아래와 같이 수정할 수 있습니다.

수정된 소스

@Setter
@OneToMany(mappedBy = "productOrder", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Product> products = new ArrayList<>();

...

if (CollectionUtils.isEmpty(this.products)) {
    products.clear();
}

products.addAll(tempProducts);

위의 소스에서 new ArrayList<>() 와 같이 새로운 인스턴스를 생성 하는것이 아닌, products.clear() 를 통해서 기존 부모 Entity와 연결을 해제하지 않고 컬렉션을 비우고 새로운 값을 넣어주었다. 이렇게 하면 세션(영속성 컨텍스트)의 연결이 틀어지지않고 기존의 연결을 유지하여 데이터를 추가 및 변경하도록 수정할 수 있게 된다.