Hibernate映射集合性能问题

首先,这篇文章的灵感来自于Burt Beckwith2011年1月27日SpringOne 2GX上发表的有关高级GORM –性能,定制和监控演讲 。 简而言之, Burt Beckwith讨论了使用映射集合和GORM中的Hibernate 2级缓存的潜在性能问题,以及避免此类性能下降的策略。

尽管如此, Burt Beckwith在演讲中指出的有关映射集合的性能问题通常适用于每个启用Hibernate的应用程序。 这就是为什么在观看他的演示文稿后,我才意识到他的提议正是我自己一直在做的事情,并指示我的同事在使用Hibernate中的 映射集合进行开发时应该做。

以下是使用Hibernate 映射集合时要考虑的5件事:

让我们考虑以下经典的“图书馆–访问”示例:

以下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项目的插入和删除。 提议的解决方案通过以下方式影响使用:

  1. 要将访问添加到库中,我们必须创建一个新的“ 访问”项,将其与“ 库”项相关联,并将其显式保存在数据库中。
  2. 要从图书馆中删除访问 ,我们必须搜索“访问”表,找到我们需要的确切记录并将其删除。
  3. 使用建议的解决方案,不支持级联。 要删除资料库,您需要先删除(取消关联)其所有访问记录。

为了保持环境整洁有序,您可以通过实现一个助手方法将“访问”伪集合恢复到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将其视为新集合。

懒惰的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值