使用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共用的,即使內容一樣。
沒有留言:
張貼留言