hibernate 映射
尽管如此, Burt Beckwith在演讲中指出的有关映射集合的性能问题通常适用于每个启用Hibernate的应用程序。 这就是为什么看完他的演讲后我才意识到他的提议正是我自己一直在做的事情,并指示我的同事在使用Hibernate中的 映射集合进行开发时应该做。
让我们考虑以下经典的“图书馆–访问”示例:
以下Library类具有Visit实例的集合:
package eg;
import java.util.Set;
public class Library {
private long id;
private Set visits;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private Set getVisits() { return visits; }
private void setVisits(Set visits) { this.visits=visits; }
....
....
}
以下是Visit类:
package eg;
import java.util.Set;
public class Visit {
private long id;
private String personName;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private String getPersonName() { return personName; }
private void setPersonName(String personName) { this.personName=personName; }
....
....
}
假设一个库具有多次唯一访问,并且每次访问都与一个不同的库相关联,则可以使用如下所示的单向 一对多关联:
<hibernate-mapping>
<class name="Library">
<id name="id">
<generator class="sequence"/>
</id>
<set name="visits">
<key column="library_id" not-null="true"/>
<one-to-many class="Visit"/>
</set>
</class>
<class name="Visit">
<id name="id">
<generator class="sequence"/>
</id>
<property name="personName"/>
</class>
</hibernate-mapping>
我还将提供上述模式的表定义示例:
create table library (id bigint not null primary key )
create table visit(id bigint not null
primary key,
personName varchar(255),
library_id bigint not null)
alter table visit add constraint visitfk0 (library_id) references library
那么这张照片怎么了?
当您尝试添加到映射的集合时,可能会出现性能瓶颈。 如您所见,该集合被实现为Set 。 集合保证其所包含元素之间的唯一性。 那么, Hibernate如何知道一个新项目是唯一的以便将其添加到Set中呢? 好吧,不要惊讶; 添加到Set中需要从数据库加载所有可用项。 Hibernate将每一项与新的进行比较,以确保唯一性。 此外,以上是我们无法绕过的标准行为,即使我们由于业务规则而知道新项目是唯一的!
在我们的映射集合中使用List实现也无法解决向其添加项目时的性能瓶颈问题。 尽管列表不保证唯一性,但它们可以保证项目的顺序。 因此,为了在映射的List中保持正确的项目顺序,即使我们将其添加到列表的末尾, Hibernate也必须提取整个集合。
我认为,添加一个新的“ 访问 图书馆”的路很长,您不同意吗?
此外,上面的示例在开发中非常有效,因为我们只有几次访问。 在每个库可能有数百万次访问的生产环境中,请想象一下,当您尝试增加一个库时会降低性能!
为了克服上述性能问题,我们可以将集合映射为Bag ,这只是一个常规集合,没有顺序或唯一性保证,但是在这样做之前,请考虑以下我的最后一点。
当您从集合中删除对象或向集合中添加对象时,集合所有者的版本号会增加。 因此,当同时进行访问创建时,在Library对象上存在人为的乐观锁定异常的高风险。 我们将乐观的锁定异常描述为“人造的”,因为它们发生在集合所有者对象( Library )上,当我们从Visits集合中添加/删除项目时,我们不觉得我们正在编辑(但是我们正在!)。
我要指出的是,相同的规则适用于多对多关联类型。
那么解决方案是什么?
解决方案很简单,从所有者( Library )对象中删除映射的集合 ,然后“手动”执行Visit项目的插入和删除。 提议的解决方案通过以下方式影响使用:
- 要将访问添加到库,我们必须创建一个新的“ 访问”项,将其与“ 库”项相关联,并将其显式保存在数据库中。
- 要从图书馆中删除访问 ,我们必须搜索“访问”表,找到我们需要的确切记录并将其删除。
- 使用建议的解决方案,不支持级联。 要删除资料库,您需要先删除(取消关联)其所有访问记录。
为了保持环境整洁有序,您可以通过实现一个助手方法将“访问”伪集合恢复到Library对象,该方法将查询数据库并返回与特定Library相关的所有Visit对象。 此外,您可以在Visit项目中实现几个帮助程序方法,这些方法将执行实际的访问记录插入和删除操作。
下面,我们提供Library类, Visit类和Hibernate映射的更新版本,以便符合我们提出的解决方案:
首先更新的库类:
package eg;
import java.util.Set;
public class Library {
private long id;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
public Set getVisits() {
// TODO : return select * from visit where visit.library_id=this.id
}
....
....
}
如您所见,我们删除了映射的集合,并引入了方法“ getVisits() ”,该方法应用于返回特定Library实例的所有Visit项目(TODO注释为伪代码)。
以下是更新的Visit类:
package eg;
import java.util.Set;
public class Visit {
private long id;
private String personName;
private long library_id;
public long getId() { return id; }
private void setId(long id) { this.id=id; }
private String getPersonName() { return personName; }
private void setPersonName(String personName) { this.personName=personName; }
private long getLibrary_id() { return library_id; }
private void setLibrary_id(long library_id) { this. library_id =library_id; }
....
....
}
如您所见,我们已经在Visit对象中添加了“ library_id ”字段,以便能够将其与Library项目相关联。
最后是更新的Hibernate映射:
<hibernate-mapping>
<class name="Library">
<id name="id">
<generator class="sequence"/>
</id>
</class>
<class name="Visit">
<id name="id">
<generator class="sequence"/>
</id>
<property name="personName"/>
<property name="library_id"/>
</class>
</hibernate-mapping>
因此,永远不要在Hibernate中使用映射的集合吗?
好吧,说实话,不。您需要检查每个案例,以便决定要做什么。 如果收集的数量较小,则标准方法很好-在多对多关联方案的情况下,双方都是如此。 此外,集合将包含代理,因此在初始化之前,它们将小于实际实例。
编码愉快! 别忘了分享!
贾斯汀
聚苯乙烯
在TheServerSide上对这篇文章进行了相当长的辩论之后,一个或我们的读者Eb Bras提供了Hibernate的“技巧和窍门”的有用列表,让他看看该说些什么:
这是我长期记录的一些Hibernate提示和技巧:
反=“真”
在一对多的父子关联中(与另一个实体或用作实体的值类型)尽可能多地使用它。
该属性在集合标签(如“ set”)上设置,表示多对一拥有关联,并负责所有数据库的插入/更新/删除。 它使关联成为孩子的一部分。 它将保存外键的数据库更新,因为它将在插入子代时直接发生。
尤其是在使用“集合”作为映射类型时,它可以提高性能,因为不需要将子级添加到父级集合中,这样可以节省整个集合的负载。 那就是:由于集合映射的性质,添加新子元素时必须始终加载整个集合,因为这是hibernate可以确保新条目不是重复项的唯一方法,这是JRE Set的功能接口。
如果它涉及一个组件集合(=仅包含纯值类型的集合),则inverse = true会被忽略并且没有意义,因为Hibernate对对象具有完全控制权,并将选择执行其操作的最佳方法。
如果涉及分离的DTO对象(不包含任何Hibernate对象),则Hibernate将删除所有值类型子对象,然后插入它们,因为它不知道哪个对象是新对象或存在对象,因为它已完全分离。 Hibernate将其视为新集合。
懒惰的Set.getChilds()是邪恶的
使用getChilds()会返回一个Set并会延迟加载所有子项,请务必小心。
当您只想添加或删除孩子时,请勿使用此功能
始终实现equals / hashcode
确保始终对Hibernate管理的每个对象实施equals / hashcode,即使它看起来并不重要。 对于值类型对象也是如此。
如果对象不包含作为equals / hashcode候选者的属性,请使用代理**,例如,由UUID组成。 Hibernate使用equals / hashcode找出数据库中是否已经存在对象。 如果它涉及到一个现有对象,但Hibernate认为它是一个新对象,因为equals / hashcode没有正确实现,则Hibernate将执行插入操作,并可能删除旧值。 特别是对于Set中的值类型而言,这一点很重要,必须进行测试,因为它可以节省数据库流量。 想法:您正在给Hibernate提供更多的知识,以便它可以用来优化他的动作。
使用版本
始终将version属性与实体或用作实体的值类型一起使用。
由于Hibernate使用此信息来发现它是否涉及新对象或现有对象,因此这将减少数据库流量。 如果不存在此属性,则必须命中数据库以查找是否涉及新对象或现有对象。
渴望获取
默认情况下,非延迟集合(子项)是通过额外选择查询加载的,该查询仅在从数据库加载父项之后才执行。
通过启用热切获取,可以通过加载集合映射标签上的属性“ fetch = join”来完成与加载父项相同的查询。 如果启用,则通过左外部联接加载子项。 测试这是否可以提高性能。 如果发生许多联接,或者如果它涉及具有许多列的表,则性能将变差而不是变好。
在值类型子对象中使用代理键
Hibernate将在由所有非空列组成的父子关系的值类型子项中构造主键。 这可能会导致奇怪的主键组合,尤其是在涉及日期列时。 日期列不应该是主键的一部分,因为它的毫秒部分将导致几乎绝不相同的主键。 这将导致奇怪的数据库性能,并且可能导致性能下降。
为了改善这一点,我们在所有子值类型对象中使用代理键,这是唯一的非null属性。 然后,Hibernate将构造一个由外键和代理键组成的主键,该主键是逻辑上的且性能良好。 请注意,代理键仅用于数据库优化,并且不需要在可能包含业务逻辑的equals / hashcode中使用。
翻译自: https://www.javacodegeeks.com/2011/02/hibernate-mapped-collections.html
hibernate 映射