修復 HibernateException:非法嘗試將集合與兩個開啟的會話關聯
1.概述
在這個簡短的教程中,我們將重點介紹如何修復HibernateException: Illegal attempt to associate a collection with two open sessions
。
我們將首先檢查異常的根本原因。然後,透過實際範例,示範如何重現該異常,以及最重要的,如何修復該異常。
2. 理解HibernateException
通常,正如名稱所暗示的那樣,「 HibernateException: Illegal attempt to associate a collection with two open sessions”
**發生在使用@OneToMany,
映射的延遲載入集合在其原始 Hibernate 會話之外被修改時**。
Hibernate 將集合與載入它們的會話緊密耦合,因此嘗試在另一個會話中存取或修改此類集合會導致衝突。例如,當我們對分離實體使用現已棄用的update()
方法時就會發生這種情況,Hibernate 不再建議這樣做。
理解此異常的含義對於正確處理基於 Hibernate 的應用程式中會話邊界和延遲載入行為至關重要。因此,讓我們深入研究,看看如何透過實際範例重現並修復HibernateException
。
3. 實例
首先,讓我們考慮一下Author
JPA 實體類別:
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Book> books;
public Author(String name) {
this.name = name;
this.books = new ArrayList<>();
}
// empty constructor, standard getters and setters
}
簡而言之,每個作者都有一個唯一的識別碼和一個名字。 @Entity
註解將Author
類別指定為 JPA 實體,而@Id
標記主鍵。 @OneToMany
註解描述了作者與其書籍之間的關聯。
接下來,我們來定義Book
實體類別:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
@ManyToOne
private Author author;
public Book(String title) {
this.title = title;
}
// empty constructor, standard getters and setters
}
這裡, @ManyToOne
表示Book
類別與Author
類別之間存在多對一關係。這意味著一個給定的作者可能有多本書。
現在,讓我們使用測試案例重現HibernateException
:
@Test
void givenAnEntity_whenChangesSpanMultipleHibernateSessions_thenThoseChangesCanNotBeUpdated() {
assertThatThrownBy(() -> {
try (Session session1 = HibernateUtil.getSessionFactory()
.openSession();
Session session2 = HibernateUtil.getSessionFactory()
.openSession()) {
session1.beginTransaction();
Author author = new Author("Leo Tolstoy");
session1.persist(author);
session1.getTransaction()
.commit();
session2.beginTransaction();
author.getBooks()
.add(new Book("War and Peace"));
session2.update(author); // oops!
session2.getTransaction()
.commit();
}
}).isInstanceOf(HibernateException.class)
.hasMessageContaining("Illegal attempt to associate a collection with two open sessions");
}
在這個測試案例中, uthor
實例在session1
中被加載,然後傳遞到session2,
並在 session2 中更新了其書籍清單。使用update()
會導致 Hibernate 拋出異常,因為書籍集合仍然與原始會話 ( session1)
關聯。
請注意,問題的主要原因不僅是會話處理不當,還在於使用了已棄用的update()
方法,該方法假定實體最初是在目前會話中載入的。
4.解決方案
最直接的解決方案是使用merge()
方法,該方法可以安全地將分離的實體(及其集合)重新附加到新會話,而不是使用已棄用的update()
方法:
@Test
void givenAnEntity_whenChangesSpanMultipleHibernateSessions_thenThoseChangesCanBeMerged() {
try (Session session1 = HibernateUtil.getSessionFactory()
.openSession();
Session session2 = HibernateUtil.getSessionFactory()
.openSession()) {
session1.beginTransaction();
Author author = // populate
session1.persist(author);
session1.getTransaction()
.commit();
session2.beginTransaction();
Book newBook = // populate
author.getBooks()
.add(newBook);
session2.merge(author); // merge instead of update
session2.getTransaction()
.commit();
}
}
簡而言之, the merge()
方法將分離的作者(在session1
中載入的作者)與新會話session2
重新關聯。
簡單來說,它將分離實例的狀態複製到目前會話中的新的託管實例中。這樣,新實例及其集合僅與當前會話關聯。
如果我們不想使用merge()
方法,我們可以簡單地將整個操作保留在一個會話中。這樣,我們就可以防止書籍集合同時與多個會話關聯:
@Test
void givenAnEntity_whenChangesUseTheSameHibernateSession_thenThoseChangesCanBeUpdated() {
try (Session session1 = HibernateUtil.getSessionFactory()
.openSession()) {
session1.beginTransaction();
Author author = // populate as before
session1.persist(author);
Book newBook = // populate as before
author.getBooks()
.add(newBook);
session1.update(author); // use the same session
session1.getTransaction()
.commit();
}
}
正如預期的那樣,新的測試案例成功運行,證實我們的修復有效地防止了HibernateException.
5. 結論
在這篇短文中,我們探討了HibernateException: Illegal attempt to associate a collection with two open sessions
。在此過程中,我們演示瞭如何在實踐中重現該異常,並提供了清晰的解決方案。