Hibernate的优化方案
一般而言,我们可以从以下两方面来优化Hibernate。
- HQL语句的优化;
- 一级缓存的优化。
下面我就稍微从这两方面说道说道,如有不足之处,还请指出。
HQL语句的优化
HQL语句可以从以下几个方面进行优化:
- 使用参数绑定
- 使用参数绑定的原因是让数据库一次解析SQL,对后续的重复请求可以使用生成好的执行计划,这样做节省CPU时间和内存;
- 避免SQL注入。
- 尽量少使用NOT:如果where子句中包含not关键字,那么执行时该字段的索引失效;
- 尽量使用where来替换having:having在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销;
- 减少对表的查询:在含有子查询的HQL中,尽量减少对表的查询,降低开销;
- 使用表的别名:当在HQL语句中连接多个表时,使用别名,提高程序阅读性,并把别名前缀与每个列连接上,这样一来,可以减少解析时间并减少列歧义引起的语法错误;
- 实体的更新与删除:在Hibernate3以后支持HQL的update与delete操作。
一级缓存的优化
一级缓存也叫做Session缓存,在一个Hibernate Session中有效,这级缓存的可干预性不强,大多于Hibernate自动管理,但它提供清除缓存的方法,这对大批量增加(更新)操作而言是有效果的,例如,同时增加十万条记录,按常规进行,很可能会出现异常,这时可能需要手动清除一级缓存,session.evict以及session.clear。
Hibernate的抓取策略(对于查询关联对象的一种优化)
延迟加载
延迟加载是Hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。说人话就是:延迟加载也叫懒加载(lazy),当执行到该行代码的时候,不会发送语句去进行查询,只有在真正使用这个对象的其他属性的时候才会真正发送SQL语句进行查询。还记得load方法采用的策略是延迟加载,get方法采用的策略是立即加载吗?延迟加载可分为两类,它们分别是:
- 类级别的延迟加载;
- 关联级别的延迟加载。
下面我就简单说说它俩。
类级别的延迟加载
类级别的延迟加载指的是通过load方法查询某个对象的时候,是否采用延迟(默认采用的就是延迟加载)。例如,
Customer c = session.load(Customer.class, 1l);
类级别的延迟加载可以通过<class>
元素的lazy属性来设置,默认值是true。即默认是延迟加载。如果为了显示的声明出来,那么我们可以在映射配置文件中设置如下,注意:<class>
元素上的lazy属性只对普通属性有效,对关联对象无效。
如果想让类级别的延迟加载失效,那么该咋办呢?总共有三种方式可让类级别的延迟加载失效,下面我会分别介绍它们。
- 第一种方式:将
<class>
元素上的lazy属性设置为false。
为了便于演示这种方式,我们在com.meimeixia.hibernate.demo02包下编写一个LoadTest单元测试类,并在该类中编写如下demo01()测试方法。
以上程序证明了如果将<class>
元素上的lazy属性设置为false,代表类级别的延迟加载失效,这时load与get方法就完全一样了,都是立即加载。
虽然我们是知道了load方法采用的策略是延迟加载,get方法采用的策略是立即加载,但是什么时候用get方法,什么时候用load方法呢?如果你查询的数据非常大,例如说它里面有一些大的字段,这个时候建议你采用load方法,不要一上来就立即加载,把我们的内存占满,这样可以让我们的性能得到一部分的提升;如果你查询的数据非常少,直接get就无所谓了,因为它不会占用我们很多的内存。 - 第二种方式:将持久化类使用final修饰,例如:
- 第三种方式:调用Hibernate.initialize()方法,例如:
Hibernate这个框架是在dao层进行操作的,如果说我现在采用了一个load的方案去获取了一个对象,我们最终会把Session关闭再返回,那么我们就要把这个对象返回到service层,最后再返回到web层,这个时候load出来的代理对象其实还没有对数据进行初始化,也即它里面还没有真正有数据,返回的时候就出问题了,那如何对一个延迟的代理对象进行初始化呢?这个时候,你就要调用Hibernate.initialize()方法对load出来的代理对象初始化一把了。
关联级别的延迟加载
查询到某个对象,获得其关联的对象或属性,这种就称为关联级别的延迟加载。说得更大白话一点:关联级别的延迟加载指的是在查询到某个对象的时候,查询其关联的对象,是否采用延迟加载。例如,
Customer customer = session.get(Customer.class, 1l);
customer.getLinkMans();//通过客户获得联系人的时候,联系人对象是否采用了延迟加载,这就称之为是关联级别的延迟加载
因为抓取策略往往会和关联级别的延迟加载一起使用,优化语句,所以下面就要研究抓取策略了。
抓取策略
抓取策略的概述
抓取策略指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。我们知道Hibernate中对象之间的关联关系有三种:
这里我们主要讲的是在<set>
与<many-to-one>
这两个标签上设置fetch、lazy属性,为何要设置这俩哥们呢?因为通过一个对象抓取到其关联对象时,需要发送SQL语句,SQL语句到底何时发送,发送成什么样的格式,这就需要通过抓取策略来进行配置了。
- fetch属性主要描述的是SQL语句的格式,例如是多条,还是子查询,还是多表联查;
- lazy属性用于控制SQL语句何时发送。
我在这里举个例子说明下,例如现在要查询一个客户,并且还要关联查询他名下的联系人。客户代表一的一方,在客户实体类中有Set集合来描述其名下的联系人,那么客户映射配置文件就应该是这样子的配置:
此时,我们就可以在set标签上设置fetch和lazy这两个属性了。
再比如,查询一个联系人时,要查询关联的客户信息。联系人代表多的一方,在联系人实体类中有Customer对象来描述其关联的客户,在联系人映射配置文件中我们使用的是<many-to-one>
标签,如下图所示。
此时,我们亦可在该标签上设置fetch和lazy这两个属性。当然了,你也可在<one-to-one>
标签上设置这两个属性。
知道抓取策略要干的事之后,接下来,我将会花大篇幅来讨论<set>
与<many-to-one>
这两个标签上的fetch属性和lazy属性到底该如何设置,以优化发送的SQL语句。
<set>
标签上的fetch与lazy
<set>
标签上的fetch和lazy这两属性主要是用于设置关联的集合信息的抓取策略。
- fetch属性:抓取策略,用来控制在查询关联对象时的SQL语句的格式。其可取值有:
- select:默认值,发送普通的select语句查询关联对象;
- join:发送一条迫切左外连接来查询关联对象;
- subselect:发送一条子查询去查询其关联对象。
- lazy:延迟加载,控制查询关联对象的时候是否延迟。其可取值有:
- true:默认值,查询关联对象的时候,采用延迟加载;
- false:查询关联对象的时候,不采用延迟加载;
- extra:极其懒惰,也就是说你用什么样的SQL语句,就发送什么样的SQL语句。
这样看来,fetch与lazy的组合就有九种了,其实不然,fetch与lazy的组合实际上只有七种,且听我娓娓道来。
第一种组合(默认情况)
在com.meimeixia.hibernate.demo02包下编写一个SetFetchTest单元测试类,并在该类中编写如下测试方法:
package com.meimeixia.hibernate.demo02;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.meimeixia.hibernate.domain.Customer;
import com.meimeixia.hibernate.domain.LinkMan;
import com.meimeixia.hibernate.utils.HibernateUtils;
/**
* 在<set>上的fetch和lazy
* @author liayun
*
*/
public class SetFetchTest {
/*
* 默认情况,即fetch="select"、lazy="true"
*/
@Test
public void demo01() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询1号客户
Customer customer = session.get(Customer.class, 1l);//只会发送一条查询客户的sql语句(根据客户的ID),作断点调试
System.out.println(customer.getCust_name());
//查询1号客户的每个联系人的信息
for (LinkMan linkMan : customer.getLinkMans()) {
//又会根据客户的ID去查询联系人的sql语句
System.out.println(linkMan.getLkm_name());
}
tx.commit();
}
}
在Customer customer = sessi