hibernate 优化总结

本文总结了Hibernate在处理大数据量时的优化策略,包括合理使用Session的clear()和evict(),避免list()和iterator()的滥用,利用scroll()方法,谨慎处理关联操作,注意集合的默认cascades,启用延迟加载机制,设置fetch_size和batch_size属性,优化主键生成机制,考虑关闭延迟加载,使用抓取策略和缓存管理等。通过这些技巧,可以显著提高Hibernate的性能和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hibernate 优化总结
一、在处理大数据量时,会有大量的数据缓冲保存在Session的一级缓存中,这缓存大太时会严重显示性能,所以在使用Hibernate处理大数据量的,可以使用session. clear()或者session. evict(Object) 在处理过程中,清除全部的缓存或者清除某个对象。
二、对大数据量查询时,慎用list()或者iterator()返回查询结果,
<1>. 使用List()返回结果时,Hibernate会所有查询结果初始化为持久化对象,结果集较大时,会占用很多的处理时间。
<2>. 而使用iterator()返回结果时,在每次调用iterator.next()返回对象并使用对象时,
Hibernate才调用查询将对应的对象初始化,对于大数据量时,每调用一次查询都会花费较多的时间。当结果集较大,但是含有较大量相同的数据,或者结果集不是全部都会使用时,使用iterator()才有优势。
<3>. 对于大数据量,使用qry.scroll()可以得到较好的处理速度以及性能。而且直接对结果集向前向后滚动。
三、对于关联操作,Hibernate虽然可以表达复杂的数据关系,但请慎用,使数据关系较为简单时会得到较好的效率,特别是较深层次的关联时,性能会很差。
四、对含有关联的PO(持久化对象)时,若default-cascade="all"或者 “save-update”,新增PO时,请注意对PO中的集合的赋值操作,因为有可能使得多执行一次update操作。
五、在一对多、多对一的关系中,使用延迟加载机制,会使不少的对象在使用时才会初始化,这样可使得节省内存空间以及减少数据库的负荷,而且若PO中的集合没有被使用时,就可减少互数据库的交互从而减少处理时间。
六、对于大数据量新增、修改、删除操作或者是对大数据量的查询,与数据库的交互次数是决定处理时间的最重要因素,减少交互的次数是提升效率的最好途径,所以在开发过程中,请将show_sql设置为true,深入了解Hibernate的处理过程,尝试不同的方式,可以使得效率提升。
七、Hibernate是以JDBC为基础,但是Hibernate是对JDBC的优化,其中使用Hibernate的缓冲机制会使性能提升,如使用二级缓存以及查询缓存,若命中率较高明,性能会是到大幅提升。
八、Hibernate可以通过设置hibernate.jdbc.fetch_size,hibernate.jdbc.batch_size等属性,对Hibernate进行优化。
九、不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,大量并发insert数据时可能会引起表之间的互锁。数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量),之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成机制。
十、Dynamic Update 如果选定,则生成Update SQL 时不包含未发生变动的字段属性,这样可以在一定程度上提升SQL执行效能.Dynamic Insert 如果选定,则生成Insert SQL 时不包含未发生变动的字段属性,这样可以在一定程度上提升SQL执行效能
十一、在编写代码的时候请,对将POJO的getter/setter方法设定为public,如果设定为private,Hibernate将无法对属性的存取进行优化,只能转而采用传统的反射机制进行操作,这将导致大量的性能开销(特别是在1.4之前的Sun JDK版本以及IBM JDK中,反射所带来的系统开销相当可观)。
十二、在one-to-many 关系中,将many 一方设为主动方(inverse=false)将有助性能的改善。
十三、由于多对多关联的性能不佳(由于引入了中间表,一次读取操作需要反复数次查询),因此在设计中应该避免大量使用
十四、延迟加载
1、 延迟加载
延迟加载(load)是Hibernate为提高程序执行效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
场合一:当用户要取数据库的一张表的一个字段,这个字段很可能就是一个字符,总而言之长度是比较短的。
场合二:当用户要取数据库的一张表的一个字段的值,而这个值很可能是blob类型,也许存取的是一个很大的视频文件
2、 关闭延迟加载
在加载单个实体时,如果不需要延迟加载,就可以使用session的get()方法。
十五、抓取策略
在HQL语句中使用抓取连接查询,通过写一条left join fetch 语句把相关联的两个实体的数据一次性从数据库中加载上来。这样可以在特定情况下(同时需要使用到这两个实体的数据)减少SQL的数量来提高查询效率。
通过配置“抓取策略”来直接影响session的get()和load()方法的查询效果。
1.单端关联<many-to-one><one-to_one>上的抓取策略。
可以给单端关联的映射元素添加fetch属性。fetch属性有2个可选值
  select:作为默认值,它的策略是当需要使用到关联对象的数据时,另外单独发送一条select语句抓取当前对象的关联对象的数据。即延迟加载。
  join:它的策略是在同一条select语句使用连接来获得对象的数据和它关联对象的数据,此时关联对象的延迟加载失效。
  2.集合属性上的抓取策略
在集合属性的映射元素上可以添加fetch属性,它有3个可选值。
 select:作为默认值,它的策略是当需要使用所关联集合的数据时,另外单独发送一条select语句抓取当前对象的关联集合,即延迟加载。
join:在同一条select语句使用连接来获得对方的关联集合。此时关联集合上的lazy会失效。
subselect:另外发送一条查询语句(或子查询语句)抓取在前面查询到的所有实体对象的关联集合。这个策略对HQL的查询也起作用。
当fetch为join时,执行左外连接,这个时候,在加载Customer时,Customer所对应的Order值全部被加载到缓存中,如果Order中没有大数据,这个策略是一个不错的选择。
当fetch为subselect时,针对in有效,如果为select时,from Customer where id in(1,2,3),hibernate会把ID取出来,逐一的去取Order的值,效率比较低。这个时候subselect效率比较高,不管in里含有多少数据,在查询Order是,只会发出一条sql语句。把<set>集合中batch-size设置为一个比较合适的数值时也相当于fetch为subselect,你可以根据项目的因素来选择发出sql语句的次数。
HQL总是忽略映射文件中设置的预先抓取策略,即在HQL中使用预先抓取时,必须显示指明fetch关键字。然而不同的是,QBC则不会忽略映射文件中的预先抓取策略。
在实践开发项目过程中,不仅需要根据实际情况选择合适的抓取策略,而且需要通过不断的测试来验证这个策略是不是最有效率的。

十六、缓存管理
1.缓存概述
  缓存(cache)在Java应用程序中是一组内存中的集合实例。它保存着永久性存储源(如硬盘上的文件或者数据库)中数据的备份,它的读写速度比读写硬盘的速度快。应用程序在运行时直接读写缓存中的数据,只在某些特定时刻安装缓存中的数据来同伴更新数据存储源。如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
   缓存的作用就是降低应用程序直接读写永久性数据存储源的频率,从而增强应用的运行性能。
缓存的实现不仅需要作为物理介质的硬件(内存),同时还需要用于管理缓存并发访问和过期等策略的软件。

2 缓存范围分类
缓存的范围决定了缓存的生命周期及其可以被谁访问。缓存的范围分为以下三类:
1)事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。
2)进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。
3)集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据一致性。对于大多数应用来说,应该慎用集群范围的缓存,因为访问的速度并不一定比直接访问数据库数据的速度快很多。

3. 缓存的并发访问策略
在进程范围或集群范围的缓存,会出现并发问题。因此可以设置4中类型的并发访问策略,每一种策略对应一种事务隔离级别。事务的隔离级别越高,并发性能就越低。
1)事务型(Transactional)策略
2)读写型(Read-Write)策略
3)非严格读写型(Nonstrict-read-write)策略
4)只读型(Read-only)策略

4.Hibernate中的缓存
Hibernate中提供两级缓存,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。
第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围范围的缓存,这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
Hibernate还为查询结果提供一个查询缓存,它依赖于二级缓存。


 

5.一级缓存的管理
Session级别的缓存由hibernate自动管理。当应用程序调用Session的CRUD方法及调用查询接口的list(),iterate()等方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把改对象加入到Session缓存中。如果在Session缓存中已经存在这个对象,就不需要再去数据库加载而是直接使用缓存中的这个对象,可以减少访问数据库的频率,提高程序的运行效率。当Hibernate清理缓存时(默认是提交事务的时候),Hibernate会根据缓存中对象的状态来同步数据库中的数据状态,在关闭Session时,会清空Session缓存中的所有对象。
一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出;可以用
evict(Object obj):从缓存中清除指定的持久化对象。
clear():清空缓存中所有持久化对象。
 flush(): 进行清理缓存(此时缓存中的数据并不丢失)的操作,让缓存和数据库同步 执行一些列sql语句,但不提交事务。
 commit():先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。

可以写一个for循环,Session可以批量插入上万条数据。如下面的代码:
             For(int i=0;i<10000;i++){
  Session.save(object);
}
这样写的代码会产生一个什么问题?会使一万个对象的缓存全部存在于内存中,这样做加大了内存的压力。所以应该定期清理session的缓存。
当做批量插入或批量更新时,必须通过经常调用Session的flush()以及稍后调用clear()来控制一级缓存的大小,这样内存才能保证足够的空间。

for(int i=1;i<=50;i++){
    Department dept = new Department();
    dept.setName("软件"+i);
    session.save(dept);
    for(int j=1;j<=100;j++){
        Employee emp = new Employee();
        emp.setName("张三"+j);
        emp.setSalary(j);
            emp.setDept(dept
            session.save(emp);
            if(j%50==0){
                session.flush();
                session.clear();
            }
    }
}
6.二级缓存的管理
二级缓存是SessionFactory级别的缓存,它的使用过程如下:
1)执行条件查询的时候,发出“select * from table_name where …”这样的SQL语句查询数据库,一次获得所有的数据对象。
2)把获得的所有数据对象根据ID放入到二级缓存中。
3)当Hibernate根据ID访问数据对象时,首先从Session缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;还没查到,再查询数据库,把结果按照ID放入到二级缓存。
4)删除、更新和增加数据的时候,同时会更新到二级缓存中。
Hibernate的二级缓存策略是针对ID查询的缓存策略,对于调节查询则毫无作用。为此,hibernate提高了单独针对条件查询的查询缓存。
适合存放到二级缓存中的数据有:
1)很少被修改的数据。
2)不是很重要的数据,允许出现偶尔的并发的数据。
3)很多系统模块都要用到
4)不是私有的数据,是共享的

什么样的数据不适合放在二级缓存中???
财务数据  安全性的数据 也就是不想让别人看到的数据和特别重要的数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值