hibernate性能优化

hiberante的性能优化主要是对数据库连接池技术、hibernate二级缓存、hibernate的配置优化等问题进行学习!
通常的Hibernate的优化策略:
    1.抓取优化
    抓取是指Hibernate如何在关联关系之间进行导航的时候,Hibernate如何获取关联对象的策略,其主要定义了两个方面:如何抓取和何时抓取
    1)如何抓取。
    Hibernate3主要有两种种抓取方式,分别应用于对象关联实例(many-to-one、one-to-one)和对象关联集合(set、map等),总共是四种变种
    JOIN抓取: 通过在SELECT语句中使用OUTER JOIN来获得对象的关联实例或者关联集合)
    SELECT抓取: 另外发送一条SELECT语句来抓取当前对象的关联实体和集合
    在我的开发经历中,此处对性能的优化是比较有限的,并不值得过多关注
    例:
    A.应用于对象关联实例(默认是false)
    <many-to-one name=".." outer-join="true/false/auto"  .../> 
    B.应用于对象关联集合(默认是auto)
    <set name=".." fetch="join/select" ... >
       ....
    </set>
    2)何时抓取
    主要分为延迟加载和立即抓取,默认的情况下Hibernate3对对象关联实采用延迟加载,普通属性采用立即抓取,通过延迟加载和采用适当的抓取粒度,与不采用优化相比往往可以将性能提升数倍
    立即抓取:当抓取宿主对象时,同时抓取其关联对象和关联集以及属性
    延迟加载:当抓取宿主对象时,并不抓取其关联对象,而是当对其对象进行调用时才加载
    例:
    A.应用于对象关联实例(默认是延迟加载)
    <many-to-one name=".."  lazy="true/false" .../> 
    B.应用于对象关联集合(默认是延迟加载)
    <set name=".." lazy="true/false" ... >
       ....
    </set>
    对于延迟加载,需要注意的时,对延迟对象的使用必须在Session关闭之前进行,Hibernate的 LazyInitalizationException往往就是由于在Session的生命期外使用了延迟加载的对象。当我们进行Web开发时,可以使用 OpenSessionInView模式,当请求开始时打开session,当请求响应结束时才关闭session,不过,在使用 OpenSessionInView模式时,需要注意如果响应时间比较长(业务比较复杂或者客户端是低速网络),将Session资源(也就是数据库的连接)占用太久的话可以会导致资源耗尽
    3)抓取粒度
    抓取粒度指的是对象在关联关系之间被导航时一次预先加载的数量,Hibernate程序的性能比较差往往就在于没有对抓取粒度仔细考虑,当加载一个列表并在列表中的每个对象中对其关联进行导航时,往往导致N+1条SQL语句查询。
    例:
    A.应用于对象关联实例(默认为1),注意,对对象关联实例的设 置是在被关联的对象之上的,譬如
    class User
    {
        Group g;
    }
    那么抓取粒度应该在Group的配置文件之上,见下
    <class name="Group" table="group" batch-size="..">
        ...
    </class>
    对该值并没有一个约定俗成的值,根据情况而定,如果被关联表数据比较少,则可以设置地小一些,3-20,如果比较大则可以设到30-50,注意的时候,并不是越多越好,当其值超过50之后,对性能并没有多大改善但却无谓地消耗内存
    假设有如下例子:
       List<User> users = query.list();
    如果有20个User,并对这20个User及其Group进行遍历,如果不设置batch-size(即batch-size="1"),则在最糟糕的情况
    下,需要1 + 20条SQL语句,如果设置batch-size="10",则最好的情况下只需要1 + 2条SQL语句
    B.应用于对象关联集合(默认为1)
    <set name=".." batch-size="" ... >
       ....
    </set>
    2.二级缓存
    Hibernate 对数据的缓存包括两个级:一级缓存,在Session的级别上进行,主要是对象缓存,以其id为键保存对象,在Session的生命期间存在;二级缓存,在SessionFactory的级别上进行,有对象缓存和查询缓存,查询缓存以查询条件为键保存查询结果,在SessionFactory的生命期间存在。默认地,Hibernate只启用一级缓存,通过正确地使用二级缓存,往往可以获得意想不到的性能。
    1)对象缓存:
    当抓取一个对象之后,Hiberate将其以id为键缓存起来,当下次碰到抓取id相同的对象时,可以使用如下配置
    方法1:在缓存对象上配置
    <class ...>
       <cache useage="read-only/write/...." regions="group" />
    </class>
    useage 表示使用什么类型的缓存,譬如只读缓存、读写缓存等等(具体参见Hibernate参考指南),值得注意的时,有部分缓存在Hibernate的实现中不支持读写缓存,譬如JBossCache在Hibernate的实现中只是一种只读缓存,具体缓存实现对缓存类型的支持情况,可以参见 org.hibernate.cache包
    regions表示缓存分块,大部分的缓存实现往往对缓存进行分块,该部分是可选的,详细参见各缓存实现
    方法2:在hibernate.cfg.xml中配置
    <cache class=".." useage=".." regions=".."/>
    我认为第二种更好,可以统一管理
    2)查询缓存
    查询时候将查询结果以查询条件为键保存起来,需要配置如下
    A.在hibernate.cfg.xml中配置(启用查询缓存)
    <property name="hibernate.cache.use_query_cache">true</property>  (前面的属性名可参见常量
org.hibernate.cfg.Enviroment.USE_QUERY_CACHE)
    B.程序
    query.setCacheable(true);
    query.setCacheRegions(...);
    需要注意的是,查询缓存与对象缓存要结合更有效,因为查询缓存仅缓存查询结果列表的主键数据
    一般情况下在开发中,对一些比较稳定而又被频繁引用的数据,譬如数据字典之类的,将其进行二级缓存,对一些查询条件和查询数据变化不频繁而又常常被使用的查询,将其进行二级缓存。由于二级缓存是放在内存中,而且Hibernate的缓存不是弱引用缓存(WeekReference),所以注意不要将大块的数据放入其中,否则可能会被内存造成比较大的压力。
    3.批量数据操作
    当进行大批量数据操作(几万甚至几十几百万)时,需要注意两点,一,批量提交,二,及时清除不需要的一级缓存数据
    1)所谓的批量提交,就是不要频繁使用session的flush,每一次进行flush,Hibernate将PO数据于数据库进行同步,对于海量级数据操作来说是性能灾难(同时提交几千条数据和提交一条数据flush一次性能差别可能会是几十倍的差异)。一般将数据操作放在事务中,当事务提交时 Hibernate自动帮你进行flush操作。
    2)及时清除不需要的一级缓存数据:由于Hibernate默认采用一级缓存,而在 session的生命期间,所有数据抓取之后会放入一级缓存中,而当数据规模比较庞大时,抓取到内存中的数据会让内存压力非常大,一般分批操作数据,被一次操作之后将一级缓存清除,譬如
    session.clear(User.class) 或者 session. Evict(Object)
    4.杂项
    dynamic-insert,dynamic-update,动态插入和动态更新,指的是让Hibernate插入数据时仅插入非空数据,当修改数据时只修改变化的数据,譬如对于 
    class User
    {
       id
       username
       password
    }
    如果u.id=1, u.username="ayufox",u.password=null,那么如果不设置动态插入,则其sql语句是 insert into users(id, username, password) values (1, 'ayufox', '),如果设置则其 sql语句是insert into users(username) valeus('ayufox')
    在如上的情况下,如果修改 u.password='11',那么如果不设置动态更新,则sql语句为 update users set username='ayufox', password='11' where id = 1,如果设置则为 update user set password='11' where d = 1
    设置是在class的映射文件中,如下
    <class name="User" table="users" dynamic=insert="true/false" dynamic-update="true/false" ...>
    </class>
  该设置对性能的提升比较有限

 
 
Hibernate 性能优化补充

2) 对大数据量查询时,慎用 list() 或者 iterator() 返回查询结果,
1. 使用 List() 返回结果时, Hibernate 会所有查询结果初始化为持久化对象,结果集较大时,会占用很多的处理时间。
2. 而使用 iterator() 返回结果时,在每次调用 iterator.next() 返回对象并使用对象时, Hibernate 才调用查询将对应的对象初始化,对于大数据量时,每调用一次查询都会花费较多的时间。当结果集较大,但是含有较大量相同的数据,或者结果集不是全部都会使 用时,使用 iterator() 才有优势。
3. 对于大数据量,使用 qry.scroll() 可以得到较好的处理速度以及性能。而且直接对结果集向前向后滚动。

3) 对于关联操作, Hibernate 虽然可以表达复杂的数据关系,但请慎用,使数据关系较为简单时会得到较好的效率,特别是较深层次的关联时,性能会很差。

4) 对含有关联的 PO (持久化对象)时,若 default-cascade="all" 或者 “save-update” ,新增 PO 时,请注意对 PO 中的集合的赋值操作,因为有可能使得多执行一次 update 操作。

5) 在一对多、多对一的关系中,使用延迟加载机制,会使不少的对象在使用时 才 会初始化,这样可使得节省内存空间以及减少 数据库的负荷,而且若 PO 中的集合没有被使用时,就可减少互数据库的交互从而减少处理时间。


6) 不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,大量并发 insert 数据时可能会引起表之间的互锁。数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的 最大值和递增量),之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次 Insert 操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。
因此,对于并发 Insert 要求较高的系统,推荐采用 uuid.hex 作为主键生成机制。
7) 在编写代码的时候请,对将 POJO 的 getter/setter 方法设定为 public ,如果设定为 private , Hibernate 将无法对属性的存取进行优化,只能转而采用传统的反射机制进行操作,这将导致大量的性能开销(特别是在 1.4 之前的 Sun JDK 版本以及 IBM JDK 中,反射所带来的系统开销相当可观)。
8) 在 one-to-many 关系中,将 many 一方设为主动方( inverse=false )将有助性能的改善
9) 由于多对多关联的性能不佳(由于引入了中间表,一次读取操作需要反复数次查询),因此在 设计中应该避免大量使用 .
10) Hibernate 支持两种锁机制:即通常所说的“悲观锁( Pessimistic Locking )”和“乐观锁( Optimistic Locking )”。 悲观锁带来 数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题.乐观锁机制避免了长事务中的数据库加锁开 销,大大提升了大并发量下的系统整体性能表现。



一。 one-to-many关系性能口诀:
1. 一般情况下避免使用单向关联,尽量使用双向关联
2. 使用双向关联,inverse=“true”
3. 在分层结构中通过DAO接口用session直接持久化对象,避免通过关联关系进行可达性持久化



二。many-to-one关系
单向many-to-one表达了外键存储方
灵活运用many-to-one可以避免一些不必要的性能问题
many-to-one表达的含义是:0..n : 1,many可以是0,可以是1,也可以是n,也就是说many-to-one可以表达一对多,一对一,多对一关系
因此可以配置双向many-to-one关系,例如:
1. 一桌四人打麻将,麻将席位和打麻将的人是什么关系?是双向many-to-one的关系

三。one-to-one
通过主键进行关联
相当于把大表拆分为多个小表
例如把大字段单独拆分出来,以提高数据库操作的性能
Hibernate的one-to-one似乎无法lazy,必须通过bytecode enhancement

四。集合List/Bag/Set
one-to-many
1. List需要维护index column,不能被用于双向关联,必须inverse=“false”,被谨慎的使用在某些稀有的场合
2. Bag/Set语义上没有区别
many-to-many
1. Bag和Set语义有区别
2。 建议使用Set

五。集合的过滤
1. children = session.createFilter(parent.getChildren(), “where this.age > 5 and this.age < 10”).list()
针对一对多关联当中的集合元素非常庞大的情况,特别适合于庞大集合的分页:
session.createFilter(parent.getChildren(),“”).setFirstResult(0).setMaxResults(10).list();
在hibernate 中用 super.getSession().createFilter( , )

六。继承关系当中的隐式多态
HQL: from Object
1. 把所有数据库表全部查询出来
2. polymorphism=“implicit”(default)将当前对象,和对象所有继承子类全部一次性取出
3. polymorphism=“explicit”,只取出当前查询对象
 

总结:
性能口诀
1
、使用双向一对多关联,不使用单向一对多
2
、灵活使用单向多对一关联
3
、不用一对一,用多对一取代
4
、配置对象缓存,不使用集合缓存
5
、一对多集合使用Bag,多对多集合使用Set
6
、继承类使用显式多态
7
、表字段要少,表关联不要怕多,有二级缓存撑腰

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值