Hibernate 的“Detached Entity Passed to Persist”錯誤
一、概述
在本文中,我們將了解 Hibernate 的PersistentObjectException
,它在嘗試保存分離的實體時發生。
我們將首先了解detached
狀態的含義以及 Hibernate 的persist
化和merge
方法之間的區別。之後,我們將在各種用例中重現該錯誤並查看如何修復它。
2. 分離實體
讓我們先簡要回顧一下detached
狀態是什麼以及它與實體生命週期的關係。
detached
的實體是不再被persistence context
跟踪的 Java 對象。如果我們關閉或清除會話,實體可以達到這種狀態。類似地,我們可以通過手動將實體從持久性上下文中移除來分離實體。
我們將在本文中的代碼示例中使用Post
和Comment
實體。要分離特定的Post
實體,我們可以使用session.evict(post)
。此外,我們可以通過使用session.clear()
清除會話來從上下文中分離所有實體。
例如,一些測試將需要一個分離的Post
。那麼,讓我們看看我們如何實現這一點:
@Before
public void beforeEach() {
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
this.detachedPost = new Post("Hibernate Tutorial");
session.persist(detachedPost);
session.evict(detachedPost);
}
首先,我們持久化Post
實體,然後使用session.evict(post)
將其分離。
3. 試圖堅持一個分離的實體
如果我們嘗試持久化一個分離的實體,Hibernate 將拋出一個PersistenceException
並帶有“分離的實體傳遞給持久化”錯誤消息。
讓我們嘗試持久化一個分離的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");
}
為了避免這種情況,我們應該了解實體狀態並使用適當的方法來保存它。
如果我們使用 merge 方法,Hibernate 將根據**Id**
字段**merge
實體重新附加到持久化上下文**:
@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");
}
同樣,我們也可以使用其他 Hibernate 特定的方法,例如update
、 save
和saveOrUpdate
。與persist
化和merge,
這些方法不是 JPA 規範的一部分。因此,如果我們想使用 JPA 抽象,我們應該避免它們。
4. 試圖通過關聯來維持一個分離的實體
對於這個例子,我們將介紹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
}
我們可以注意到Comment
實體與Post
具有多對一的關係。
級聯類型設置為CascadeType.MERGE
。因此,我們只會將merge
操作傳播到關聯的Post
。
換句話說,如果我們merge
一個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");
}
另一方面,如果級聯類型設置為PERSIST
或ALL
,Hibernate 將嘗試在分離的關聯字段上傳播持久操作。因此,當我們使用這些級聯類型之一persist
化Post
實體時,Hibernate 將persist
化關聯的分離的Comment
, 這將導致另一個PersistentObjectException
。
5. 結論
在本文中,我們討論了 Hbernate 的PersistentObjectException
並了解了其主要原因。
我們可以通過正確使用 Hibernate 的save
、 persist
、 update
、 merge
和saveOrUpdate
方法來避免它。
此外,對 JPA 級聯類型的良好利用將防止PersistentObjectException
在我們的實體關聯中發生。
與往常一樣,本文的源代碼可 在 GitHub 上獲得。