2007-03-23

[Debug] Hibernate Many-to-many Collection

環境介紹

使用Hibernate處理many-to-many,主要物件有二:Category與Document。

Category與Document關聯為單向many-to-many。

程式如下:
// Domain objects
public class Category extends CommonBean {
private String name;
private Collection<Book> books = new ArrayList<Book>();
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(joinColumns = {
@JoinColumn(name = "categoryId")
}, inverseJoinColumns = {
@JoinColumn(name = "bookId")
})
public Collection<Book> getBooks() {
return books;
}
// ...
}

狀況描述
背景:假設Category A裡已經有三筆Book,分別為Book A、Book B與Book C。

執行:建立一Category B,並將Category A裡的三筆Book複製到Category B裡。

Category bCategory = new Category();
bCategory.setName("Science");
HibernateUtil.doAdd(bCategory, sf);

bCategory.setBooks(aCategory.getBooks());
HibernateUtil.doUpdate(bCategory, sf);

預期:Category A有三筆Book,Category B也有三筆Book,均為Book A、Book B與Book C,也就是表格Category_Book應該要有六筆資料。

結果:Category A沒有任何Book,Category B有三筆Book,分別為Book A、Book B與Book C,即表格Category_Book只有三筆資料。

問題所在
Category A從Hibernate取出來時,books那個Collection A實際上是Hibernate自訂的型別(custom Collection implementation),Hibernate之所以使用自訂的Collection是為了保留Entity與Database的關聯,用來進行像是dirty check之類的工作,所以這個Collection A等於是和Database裡的那三筆資料代表。

然後,Category A離開了session,變成detached,Category B出現了,Category A的Collection A整個被塞到Category B裡,問題就在這個動作。

最後Category B與資料庫同步了,這時候Hibernate發現Category B裡的Collection A是『自家人』,這時候有趣的事情發生了,Hibernate在更新detached的many-to-many時,都是先將『既有』的關聯全刪掉,再插入新的關聯,所以原本那三筆row就因此被刪掉了(因為Collection A那個『自家人』知道有這三筆資料的存在),最後再塞進三筆與Category B有關連的Book資料( [Gain] Hibernate Delete Orphan)。

解決方法
不可以將整個Collection塞進來,得一本一本Book搬進來,這樣Category A還是Collection A,Category B則是用新的Collection B,只不過這兩個Collection裡的都是Book A、Book B與Book C,唯一的差異就是Collection有兩個。

bCategory.getBooks().addAll(aCategory.getBooks());
延伸狀況
如果bCategory.setBooks(aCategory.getBooks())這個動作事發生在session裡,則會產生org.hibernate.HibernateException: Found shared references to a collection: collection.Category.books。由此可知,Hibernate是反對Collection共用的,即使內容一樣。

沒有留言:

張貼留言