1. Overview
1.概述
In this article, we’ll learn about Hibernate’s PersistentObjectException, which occurs when trying to save a detached entity.
在这篇文章中,我们将了解Hibernate的PersistentObjectException,它在试图保存一个分离的实体时发生。
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.
我们将首先了解detached状态是什么意思,以及Hibernate的persist和merge方法之间的区别。之后,我们将在各种用例中重现这个错误,并看看如何修复它。
2. Detached Entities
2.分离的实体
Let’s start with a short recap of what the detached state is and how it relates to the entity lifecycle.
让我们先简单回顾一下什么是detached状态以及它与实体生命周期的关系。
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.
detached实体是一个不再被持久化上下文追踪的Java对象。如果我们关闭或清除会话,实体可以达到这种状态。同样地,我们可以通过手动将实体从持久化上下文中移除来脱离。
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().
我们将使用Post和Comment实体作为本文的代码示例。对于分离一个特定的Post实体,我们可以使用session.evict(post)。此外,我们可以通过使用session.clear()清除会话来从上下文中分离所有实体。
For instance, some of the tests will require a detached Post. So, let’s see how we can achieve this:
例如,一些测试将需要一个分离的Post。因此,让我们看看如何实现这一点。
@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).
首先,我们持久化了Post实体,然后我们用session.evict(post)将其分离。
3. Trying to Persist a Detached Entity
3.试图坚持一个分离的实体
If we try to persist a detached entity, Hibernate will throw a PersistenceException with the “detached entity passed to persist” error message.
如果我们试图持久化一个分离的实体,Hibernate会抛出一个PersistenceException,并给出 “分离的实体被传递到持久化 “的错误信息。
Let’s try to persist a detached Post entity and expect this exception:
让我们尝试持久化一个分离的Post实体并期待这个异常。
@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:
如果我们使用merge方法,Hibernate将根据@Id 字段重新将实体连接到持久化上下文。
@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.
同样,我们也可以使用其他Hibernate特有的方法,比如update,save,和saveOrUpdate。与persist和merge不同,这些方法不是JPA规范的一部分。因此,如果我们想使用JPA的抽象,我们应该避免使用它们。
4. Trying to Persist a Detached Entity Through an Association
4.试图通过关联坚持一个分离的实体
For this example, we’ll introduce the Comment entity:
对于这个例子,我们将介绍Comment实体。
@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.
我们可以注意到,Comment实体与Post有一个多对一的关系。
The cascade type is set to CascadeType.MERGE. Therefore, we’ll only propagate merge operations to the associated Post.
级联类型被设置为CascadeType.merge。因此,我们只会将merge操作传播到相关的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:
换句话说,如果我们合并一个Comment实体,Hibernate将把该操作传播给相关的Post,并且这两个实体将在数据库中被更新。但是,如果我们想使用这种设置来persist一个Comment,我们就必须先merge相关的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.
另一方面,如果级联类型被设置为PERSIST或ALL,Hibernate将尝试在分离的关联字段上传播persist操作。因此,当我们persist一个具有这些级联类型之一的Post实体时,Hibernate将persist关联的分离Comment,这将导致另一个PersistentObjectException。
5. Conclusion
5.总结
In this article, we’ve discussed Hbernate’s PersistentObjectException and seen its main causes.
在这篇文章中,我们已经讨论了Hbernate的PersistentObjectException并看到了它的主要原因。
We can avoid it with proper usage of Hibernate’s save, persist, update, merge, and saveOrUpdate methods.
我们可以通过正确使用Hibernate的save, persist, update, merge, 和saveOrUpdate方法来避免它。
Moreover, good utilization of JPA cascading types will prevent PersistentObjectException from occurring in our entity associations.
此外,很好地利用JPA级联类型将防止PersistentObjectException在我们的实体关联中发生。
As always, the source code for the article is available over on GitHub.
一如既往,该文章的源代码可在GitHub上获取。