본문 바로가기
Backend/JPA

[JPA] org.hibernate.PersistentObjectException: detached entity passed to persist 에러

by Everyday Sustler 2023. 6. 6.
반응형

최근 김영한님의 책 자바 ORM 표준 JPA 프로그래밍을 보고 공부하고 있습니다.

 

gradle 기반 프로젝트 구성과 책에 기재된 persistence.xml, H2 데이터베이스 버전을 높이니 하루가 다 갔네요.

 

이제 재밌게 따라 배워볼까하는데, 어김없이 예외가 나타났습니다.

 

org.hibernate.PersistentObjectException: detached entity passed to persist

해결 방법

이에 대한 해결 방법은 아래와 같습니다.

 

1. @GeneratedValue 어노테이션을 해제합니다.

2. 수동으로 식별자를 입력했다면 persist 대신 merge 를 사용해 영속화합니다.

 

원인 분석

org.hibernate.PersistentObjectException: detached entity passed to persist 에러를 검색해보았습니다.

 

먼저 아래 링크에서는 persist 메소드가 오직 영속화 된 적 없는 새로운 객체에만 적용 가능함을 말하고 있습니다.

 

org.hibernate.PersistentObjectException: detached entity passed to persist

I had successfully written my first master child example with hibernate. After a few days I took it again and upgraded some of the libraries. Not sure what did I do but I could never make it run ag...

stackoverflow.com

 

답변에서 참고하고 있는 곳을 따라 들어가면 무려 13년 전에 작성된 질문과 답변이 있는데, 여기서 답을 찾을 수 있었습니다.

 

"detached entity passed to persist error" with JPA/EJB code

I am trying to run this basic JPA/EJB code: public static void main(String[] args){ UserBean user = new UserBean(); user.setId(1); user.setUserName("name1"); user.

stackoverflow.com

 

아래는 그 답변을 가져온 것입니다.

The error occurs because the object's ID is set. Hibernate distinguishes between transient and detached objects and persist works only with transient objects. If persist concludes the object is detached (which it will because the ID is set), it will return the "detached object passed to persist" error.

However, this only applies if you have specified the primary key to be auto-generated: if the field is configured to always be set manually, then your code works.

 

결론적으로 해당 에러가 나온 이유는 persist 대상에 대해 이미 영속성 컨텍스트에 적용되었었던 이력이 있다 판단했기 때문입니다.

 

그 이유는 @GeneratedValue 에 있습니다.

 

@GeneratedValue 어노테이션이 붙은 엔티티는 식별자(@Id) 값이 없을 것이라고 생각합니다.

 

그런데 manually 식별자 값을 넣은 상태로 persist 를 수행하면?

 

비어있어야 할 ID 필드에 이미 값이 있으니 @GeneratedValue 가 이미 동작했구나, 이미 한 번 영속화되었던 객체구나 라고 판단합니다.

 

그렇기 때문에 이를 해결하기 위해서는 두 가지 방법이 있습니다.

 

하나는 모든 식별자 값을 수동으로 입력하는 방법입니다. 자칫 setter 메소드가 복잡해질 수 있겠습니다.

 

다음으로는 @GeneratedValue를 사용하며 수동으로 입력한 식별자는 merge를 통해 영속화하는 방법이 있습니다.

 

역시 내부를 커스텀해야하는 오버헤드가 있을 것 같습니다.

 

따라서 개발하는 과제 특성에 따라 방법을 고려해 적용해보시는 것을 추천드립니다.

 

참고 사항

@GeneratedValue 어노테이션을 적용하고 merge() 메소드를 사용하면 수동으로 넣은 값은 변경됩니다.

 

아래와 같은 예제 코드를 확인해보겠습니다.

 

public static void testSave(EntityManager em){
        // save team1
        Team team1 = new Team("team1", "T1");
        em.persist(team1);

        // save member1
        Member member1 = new Member("member1", "m1");
        member1.setTeam(team1);
        em.merge(member1);

        // save member2
        Member member2 = new Member("member2", "m2");
        member2.setTeam(team1);
        em.merge(member2);

        // flush after merge
        em.flush();
 }

 

이렇게 member1, member2 식별자를 집어 넣어도 실제 데이터베이스에는 아래와 같이 UUID 값이 들어갑니다.

 

실제 데이터베이스에 입력되는 ID 값

반응형