Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

NPI – JPA Buddy – JPA (partner)
announcement - icon

JPA is huge! It covers nearly every aspect of communication between relational databases and the Java application and is deeply integrated into all major frameworks.

If you're using IntelliJ, JPA Buddy is super helpful. The plugin gently guides you through the subtleties of the most popular JPA implementations, visually reminds you of JPA features, generates code that follows best practices, and integrates intelligent inspections to improve your existing persistence code.

More concretely, it provides powerful tooling to generate Spring Data JPA repositories and methods, Flyway Versioned Migrations, Liquibase Differential Changelogs, DDL and SQL statements, DTO objects, and MapStruct interfaces.

Oh, and it actually generates JPA entities from an existing database and gradually update the data model as the database evolves! Yeah.

>> Become a lot more productive with JPA Buddy

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

November Discount Launch 2022 – TEMP TOP (NPI)
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

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 he 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("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 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.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Persistence footer banner
Comments are closed on this article!