1. Overview

In this tutorial, we’ll learn about Hibernate’s PersistentObjectException, which occurs when trying to save a detached entity.

We’ll start by understanding what the detached state means, and the difference between Hibernate’s persist and merge methods. Then we’ll reproduce the error in various use cases to demonstrate how to fix it.

2. Detached Entities

Let’s start with a short recap of what the detached state is and how it relates to the entity lifecycle.

A detached entity is a Java object that’s no longer tracked by the persistence context. Entities can reach this state if we close or clear the session. Similarly, we can detach an entity by manually removing it from the persistence context.

We’ll use the Post and Comment entities for the code examples in this article. For detaching a specific Post entity, we can use session.evict(post). We can detach all the entities from the context by clearing the session with session.clear().

For instance, some of the tests will require a detached Post. So let’s see how we can achieve this:

@Before
public void beforeEach() {
    session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();
 
    this.detachedPost = new Post("Hibernate Tutorial");
    session.persist(detachedPost);
    session.evict(detachedPost);
}

First, we persisted the Post entity, and then we detached it with session.evict(post).

3. Trying to Persist a Detached Entity

If we try to persist a detached entity, Hibernate will throw a PersistenceException with the “detached entity passed to persist” error message.

Let’s try to persist a detached Post entity to anticipate this exception:

@Test
public void givenDetachedPost_whenTryingToPersist_thenThrowException() {
    detachedPost.setTitle("Hibernate Tutorial for Absolute Beginners");

    assertThatThrownBy(() -> session.persist(detachedPost))
      .isInstanceOf(PersistenceException.class)
      .hasMessageContaining("detached entity passed to persist: com.baeldung.hibernate.exception.detachedentity.entity.Post");
}

To avoid this, we should be aware of the entity state and use the appropriate method for saving it.

If we use the merge method, Hibernate will re-attach the entity to the persistence context based on the @Id field:

@Test
public void givenDetachedPost_whenTryingToMerge_thenNoExceptionIsThrown() {
    detachedPost.setTitle("Hibernate Tutorial for Beginners");

    session.merge(detachedPost);
    session.getTransaction().commit();

    List<Post> posts = session.createQuery("Select p from Post p", Post.class).list();
    assertThat(posts).hasSize(1);
    assertThat(posts.get(0).getTitle())
        .isEqualTo("Hibernate Tutorial for Beginners");
}

Similarly, we can use other Hibernate-specific methods, such as update, save, and saveOrUpdate. Unlike persist and merge, these methods aren’t part of the JPA Specifications. Therefore, we should avoid them if we want to use the JPA abstraction.

4. Trying to Persist a Detached Entity Through an Association

For this example, we’ll introduce the Comment entity:

@Entity
public class Comment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String text;

    @ManyToOne(cascade = CascadeType.MERGE)
    private Post post;

    // constructor, getters and setters
}

We can see that the Comment entity has a many-to-one relationship with a Post.

The cascade type is set to CascadeType.MERGE; therefore, we’ll only propagate merge operations to the associated Post.

In other words, if we merge a Comment entity, Hibernate will propagate the operation to the associated Post, and both entities will be updated in the database. However, if we want to persist a Comment using this setup, we’ll first have to merge the associated Post:

@Test
public void givenDetachedPost_whenMergeAndPersistComment_thenNoExceptionIsThrown() {
    Comment comment = new Comment("nice article!");
    Post mergedPost = (Post) session.merge(detachedPost);
    comment.setPost(mergedPost);

    session.persist(comment);
    session.getTransaction().commit();

    List<Comment> comments = session.createQuery("Select c from Comment c", Comment.class).list();
    Comment savedComment = comments.get(0);
    assertThat(savedComment.getText()).isEqualTo("nice article!");
    assertThat(savedComment.getPost().getTitle())
        .isEqualTo("Hibernate Tutorial");
}

Conversely, if the cascade type is set to PERSIST or ALL, Hibernate will try to propagate the persist operation on the detached associated field. Consequently, when we persist a Post entity with one of these cascading types, Hibernate will persist the associated detached Comment, which will lead to another PersistentObjectException.

5. Conclusion

In this article, we discussed Hibernate’s PersistentObjectException and learned its main causes. We can avoid it with the proper use of Hibernate’s save, persist, update, merge, and saveOrUpdate methods.

Moreover, good utilization of JPA cascading types will prevent PersistentObjectException from occurring in our entity associations.

As always, the source code for the article is available over on GitHub.

Course – LSD (cat=Persistence)

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> CHECK OUT THE COURSE
res – Persistence (eBook) (cat=Persistence)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.