Hibernate性能优化

来源:互联网 发布:会锁机的软件下载 编辑:程序博客网 时间:2024/05/02 01:58

 由于hibernate是对jdbc的一个封装,而且在session的缓存中存在着相互关联的对象。对于一个类的访问将导致对另外一个关联类的访问,这样将导致大量的sql语句产生,所以为了提升hibernate性能减少频繁访问数据库,就需要对hibernate进行优化。
我们知道在hibernate的默认情况下,比如:在一对多的情况下,当我们访问Customer类后,又同时访问它相关联的Order类,但是如果我们不需要order的信息呢?这将导致hibernate的性能下降。
怎么解决呢?下面我们看一下hibernate加载一个对象的方式有哪些?
1.立即加载
所谓立即加载,就是指当加载完实体对象后,立即加载其关联的对象数据,在Customer.hbm.xml里的内容为: 
<set name="order" table="ord" cascade="all" lazy="false" inverse="true">
<key column="c_id"/>
<one-to-many class="com.lovo.Order"></one-to-many>
</set>
-------------------

从运行的结果中我们可以看出hibernate连续调用了两次sql,分别完成了对Customer和order对象的加载,这就是立即加载的基本原理,上面配置里面cascade="all"表示当我增删改这个实体时是不是要对相关联的实体进行增删改操作,在一般情况下用delete-update,lazy="false"表示立即加载这个对象,inverse="true"表示在一对多的情况下,把对象的加载交给多的一方进行维护。就好比胡锦涛和我们,胡主席不可能可照顾所有的人吧,得让我们自己照顾自己。呵呵,这个举例只是好理解而已。
2.延迟加载
当hibernate加载Customer对象时同时加载了关联的order对象,如果只需要读取Customer对象的信息而不需要读取order类的信息的话将会导致性能损耗,hibernate引入了延迟加载的方式避免了这一问题,
<set name="order" table="ord" cascade="all" lazy="true" inverse="true"><!--hibernate默认情况也是true-->
<key column="c_id"/>
<one-to-many class="com.lovo.Order"></one-to-many>
</set>
与立即加载不同之处在于,hibernate没有在加载Customer对象时就加载order对象并读取对象的信息,而是在调用customer.order.size()才会读取order的信息,这就是延迟加载的机制,当真正需要访问关联对象才执行sql语句进行数据读取。

3.预加载
所谓预加载就是指通过外连接(out-join)来获得对象的关联实例;
比如:Query query = session.createQuery("from Customer c left join c.order");
运行上面代码,hibernate会生成一条sql语句,相对于立即加载情况下的读取多条sql语句,预加载方式在性能上带来了更多的提升,减少了对数据库的访问次数,但是,我们发现预加载的情况下,关联对象的实例也会被创建。所以,这也是一种不推荐的加载方式,应当尽量使用延迟加载方式。
4.批量加载
hibernate中的批量加载就是批量提交多个限定条件,一次完成多个数据的读取,例如:
from Customer where c_id=1;
from Customer where c_id=2;
我们可以将其整合为一条语句:
from Customer where c_id in(1,2);
在hibernate的关联关系中,我们可以在立即加载和延迟加载里设定批量加载的数目,从而减少查询语句的数目,提高延迟加载和立即加载的性能。在映射文件中<set>元素的batch-size属性用于设置批量加载的数量,Customer类的映射文件有如下:
<set name="order" table="ord" cascade="all" lazy="true" inverse="true" batch-size="10"><!--一般小于10-->
<key column="c_id"/>
<one-to-many class="com.lovo.Order"></one-to-many>
</set>
可以发现在使用批量加载的情况下,select语句使用了in关键字来限定范围,从而减少了select语句的使用,而in所限定的范围大小与"batch-size"属性的值有关,值越小范围就越小,反之越大,一般来说batch-size属性设置为一个小于10的数值,由此可见,批量加载的方式可以成为延迟加载或立即加载的一种优化解决方案,采用这种方式抓取对象又被称为批量抓取(Batch fetching);

hibernate查询优化:
我们已经理解了hibernate中对象加载方式后,可以看出在hibernate中主要有如下几个方面来优化查询性能:
1.降低数据库的访问的次数和频率,减少select语句的数量,对延迟加载和立即加载设置批量抓取策略。
2.使用延迟加载,避免不需要的多余数据,
3.使用投影查询,避免查询数据占用缓存。
4.减少访问字段,降低访问数据库的数据量,利用query()对象的iterate()方法
query的list()和iterator方法都可以执行HQL查询语句,但是list()方法会直接操作sql访问数库,而iterator方法会根据主键字段到一、二级缓存里查找,如果有就加入到查询结果中,没有就执行额外的查询语句。
Query q1=session.createQuery("from Customer");
List it1=q1.list();
Query q2=session.createQuery("from Customer");
List it2=q2.iterate();

list()方法查询所有字段信息,而iterate()方法查询的却不是所有字段而是c_id,也就是说减少了访问字段,同时iterator()方法根据c_id再向缓存里进行查找对象,如果缓存里没有数据最后再进行数据库的访问。

hibernate缓存:
hibernate为什么要有缓存呢??和其它关系型数据库一样,
我们不可能每次相同的查询都去与数据库进行交互吧,也就是说hibernate的缓存就是一块内在空间,充当数据库在内存中的一个临时容器,当我们每次执行session.load(),save().update()时,或者query.list(),iterate()方法时,如果在Sesssion中不存在相应对象,hibernate就把该对象加入到缓存中。比如:我们查询ID为了的用户,由于第一次在缓存中没有,就与数据库进行交互,并且加入到一级缓存中,第二次呢?由于一级缓存中已经存在了,所以直接从一级缓存中获取数据,提高了查询性能。
缓存分为以下几种:
1。一级缓存:必须的,缓存中每个持久对象都对应一个OID,用clear()清除对象.
2.二级缓存:是一个处理分布式的缓存。不是必须的。在查询时首行是从一级缓存中查找,如果没有就去二级缓存中查找,二级缓存最大的好处在于提高了查询性能,但是,hibernate并没有提供二级缓存的实现,而是为第三方的缓存组件提供了接口,所以还需要配置。
3.查询缓存:使用查询缓存,如果查询结果集中包含实体,二级缓存中只会存放实体OID,如果查询部分属性,则二级缓存会存放所有属性值。hibernate默认是不进行查询缓存的。如果要进行缓存,需要调用Query.setCacheable(true)方法。
Session管理:
首先,什么是线程安全,什么是线程不安全??
线程安全:就是说可以有多个线程同时访问,
线程不安全:不能有多个线程访问。否则,会造成混乱。
在Session管理中SessionFacory是线程安全,它可以被多个线程同时访问,而Session是不安全的,以前我们是在每个方法里都会使用一个session,这样造成的结果是Session的频繁创建和销毁,内在消耗也相应增大了。在Session管理中,可以使用ThreadLocal模式,它为多线程的情况下并发访问问题提供了一种隔离机制,例如:
public static Session getSession(){
Session session=(Session)threadLocal.get();//获得session对象
if(session==null){
session = SessionFactory.openSession();
threadLocal.set(session);
}
return session;
}
ThreadLocal的使用将使得我们在线程级进行Session重用,从而充分利用一缓存中的已有的数据,避免数据库访问开销的浪费和大量临时对象的反复创建。
hibernate的批量处理:加入10000条数据
PrepareStatement pst = conn.preparedStatement(...);
for(int i=0;i<10000;i++){
pst.addBatch();
}
int[] count=pre.executeBatch();
由于会产生大量的对象存放在内存中,所以性能不好