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 上獲得。