Persistence top

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

>> CHECK OUT THE COURSE

1. Overview

In this article, 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 what's the difference between Hibernate's persist and merge methods. After that, we'll reproduce the error in various use cases and see 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 is 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). Furthermore, we can detach all 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 and expect this exception:

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

    assertThatThrownBy(() -> session.persist(detachedPost))
      .isInstanceOf(PersistenceException.class)
      .hasMessageContaining("org.hibernate.PersistentObjectException: detached entity passed to persist");
}

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 also use other Hibernate-specific methods such as update, save, and saveOrUpdate. Unlike persist and merge, these methods are not 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 notice 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 have to first 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");
}

On the other hand, 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've discussed Hbernate's PersistentObjectException and seen its main causes.

We can avoid it with proper usage 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.

Persistence bottom
Get started with Spring Data JPA through the reference Learn Spring Data JPA course: >> CHECK OUT THE COURSE
Persistence footer banner
Comments are closed on this article!