Hibernate事实:有利于双向集vs列表
Hibernate是一個很棒的ORM工具,它極大地簡化了開發(fā),但是如果您想正確地使用它,則有很多陷阱。
在大中型項目中,雙向父子關(guān)聯(lián)非常常見,這使我們能夠瀏覽給定關(guān)系的兩端。
在控制關(guān)聯(lián)的持久/合并部分時,有兩個可用選項。 其中將有負(fù)責(zé)同步收集變化的@OneToMany結(jié)束,但是這是一個低效率的做法,這是很好的描述在這里 。
最常見的方法是@ManyToOne端控制關(guān)聯(lián)并且@OneToMany端使用“ mappedBy”選項時。
我將討論后一種方法,因為就執(zhí)行的查詢數(shù)量而言,這是最常見,最有效的方法。
因此,對于雙向集合,我們可以使用java.util.List或java.util.Set。
根據(jù)Hibernate docs的說法,列表和文件包比集合更有效。
但是當(dāng)我看到以下代碼時,我仍然感到焦慮:
@Entity public class Parent {...@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true) private List children = new ArrayList()public List getChildren() { return children; }public void addChild(Child child) { children.add(child); child.setParent(this); }public void removeChild(Child child) { children.remove(child); child.setParent(null); } }@Entity public class Child {...@ManyToOne private Parent parent;public Parent getParent() { return parent; }public void setParent(Parent parent) { this.parent = parent; } }Parent parent = loadParent(parentId); Child child1 = new Child(); child1.setName("child1"); Child child2 = new Child(); child2.setName("child2"); parent.addChild(child1); parent.addChild(child2); entityManager.merge(parent);這是因為在最近五年中,當(dāng)在父關(guān)聯(lián)上調(diào)用合并操作時,我一直在插入重復(fù)的子代。 發(fā)生這種情況是由于以下問題: HHH-3332和HHH-5855 。
我最近一直在測試一些Hibernate版本,并且仍然在3.5.6、3.6.10和4.2.6版本上進(jìn)行復(fù)制。 因此,經(jīng)過5年在許多項目上看到這一點后,您了解了為什么我對使用列表與集合持懷疑態(tài)度。
這是在運行復(fù)制此問題的測試用例時得到的結(jié)果,因此添加兩個子級,我們得到:
select parent0_.id as id1_2_0_ from Parent parent0_ where parent0_.id=? insert into Child (id, name, parent_id) values (default, ?, ?) insert into Child (id, name, parent_id) values (default, ?, ?) insert into Child (id, name, parent_id) values (default, ?, ?) insert into Child (id, name, parent_id) values (default, ?, ?)僅當(dāng)合并操作從父級到子級聯(lián)時,才會出現(xiàn)此問題,并且存在以下變通辦法:
- 合并孩子而不是父母
- 在合并父母之前先讓孩子堅持
- 從父級刪除Cascade.ALL或Cascade.MERGE,因為它只影響合并操作,而不影響持久化操作。
但是所有這些都是黑客,在大型項目中很難遵循,因為許多開發(fā)人員都在相同的代碼庫上工作。
因此,我的首選方式是使用Set,即使有時它們的效率不如Lists更好,但是由于我一直偏愛正確性與性能優(yōu)化,因此最好使用Set。
當(dāng)涉及到這類問題時,最好具有代碼約定,因為它們易于添加到項目開發(fā)指南中,并且易于記憶和采用。
使用集合的一個優(yōu)點是,它迫使您定義適當(dāng)?shù)膃quals / hashCode策略(該策略應(yīng)始終包括實體的業(yè)務(wù)密鑰。業(yè)務(wù)密鑰是一種字段組合,該字段組合在父級的子級中是唯一的或唯一的,并且甚至在之前也是一致的)以及將實體持久保存到數(shù)據(jù)庫中之后)。
如果您擔(dān)心會失去以添加孩子的相同順序保存孩子的“列表”功能,那么您仍然可以為Sets模仿。
默認(rèn)情況下,集合是無序的和未排序的,但是即使您不能對它們進(jìn)行排序,也可以通過使用@OrderBy JPA注釋按給定的列對它們進(jìn)行排序,如下所示:
@Entity public class LinkedParent {...@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true) @OrderBy("id") private Set children = new LinkedHashSet();...public Set getChildren() { return children; }public void addChild(LinkedChild child) { children.add(child); child.setParent(this); }public void removeChild(LinkedChild child) { children.remove(child); child.setParent(null); } }加載父級的子級時,生成SQL類似于:
select children0_.parent_id as parent_i3_3_1_, children0_.id as id1_2_1_, children0_.id as id1_2_0_, children0_.name as name2_2_0_, children0_.parent_id as parent_i3_2_0_ from LinkedChild children0_ where children0_.parent_id=? order by children0_.id結(jié)論:
如果您的領(lǐng)域模型要求使用列表而不是集合,則將打破您的約束,不允許重復(fù)。 但是,如果您需要重復(fù)項,則仍然可以使用索引列表。 據(jù)說Bag是未排序且“無序的”(即使它按照在數(shù)據(jù)庫表中添加子的順序來檢索子)。 因此,索引列表也將是一個不錯的選擇,對嗎?
我還想提請注意一個5年的bug,它影響了多個Hibernate版本,并且是我在多個項目中復(fù)制的一個版本。 當(dāng)然,有一些解決方法,例如刪除Cascade.Merge或合并Children vs the Parent,但是有許多開發(fā)人員不知道此問題及其解決方法。
根據(jù)Hibernate docs:集是“ 表示多值關(guān)聯(lián)的推薦方法 ”,而且我已經(jīng)看到很多情況下使用Bags作為默認(rèn)雙向集合,即使無論如何集都是更好的選擇。
因此,我仍然對Bags保持謹(jǐn)慎,如果我的領(lǐng)域模型強加使用List,我總是會選擇索引的。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2013/10/hibernate-facts-favoring-bidirectional-sets-vs-lists.html
總結(jié)
以上是生活随笔為你收集整理的Hibernate事实:有利于双向集vs列表的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑的主板上怎么接线图解(电脑主板接线图
- 下一篇: 进程比线程更多资源_为什么我们不应该使用