경위

JpaRepository를 이용하여 ManyToOne 관계에 있는 두 도메인에 대한 클래스를 생성하고 DB에 저장하는 테스트를 실행했습니다.

증상

아래와 같은 문구를 띄우며 테스트 실패했습니다.
같은 객체(부모)를 참조하고 있는 서로 다른 두 개의 객체(자식)를 save(persist)하는 부분에서 에러가 발생했네요.

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: [package path]; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: [package path]

원인

@ManyToOneCascdeType옵션을 ALL로 부여해서 발생한 문제였습니다.

영속성 전이 - cascade

특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶다면 cascade를 이용한 영속성 전이 기능을 사용하면 됩니다. JPA의 cascade옵션으로 영속성 전이를 제공합니다.

쉽게 말해서 영속성 전이를 사용하면 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장할 수 있습니다.

CascadeType의 종류

  • CascadeType.RESIST: 엔티티를 생성하고, 연관 엔티티를 추가하였을 때 persist() 를 수행하면 연관 엔티티도 함께 persist()가 수행된다. 만약 연관 엔티티가 DB에 등록된 키값을 가지고 있다면 detached entity passed to persist Exception이 발생한다.
  • CascadeType.MERGE: 트랜잭션이 종료되고 detach 상태에서 연관 엔티티를 추가하거나 변경된 이후에 부모 엔티티가 merge()를 수행하게 되면 변경사항이 적용된다.(연관 엔티티의 추가 및 수정 모두 반영됨)
  • CascadeType.REMOVE: 삭제 시 연관된 엔티티도 같이 삭제됨
  • CascadeType.DETACH: 부모 엔티티가 detach()를 수행하게 되면, 연관된 엔티티도 detach() 상태가 되어 변경사항이 반영되지 않는다.
  • CascadeType.ALL: 모든 Cascade 적용

JPA Life cycle

CascadeType.ALL옵션을 주었기 때문에, 처음 save()했을 때 자식 객체에서 참조하고 있는 부모객체가 detached되었고, 다시 persist가 불가능 했기에 에러가 발생했습니다.

해결법

casecade 옵션을 MERGED 혹은 DETACH변경하거나 삭제합니다.

@ManyToOne /*(cascade = CascadeType.ALL)*/
@ManyToOne (cascade = CascadeType.MERGED)
@ManyToOne (cascade = CascadeType.DETACH)

References