延迟加载与抓取策略
延迟加载,也叫懒加载。它是Hibernate为提高程序执行效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
Hibernate中主要是通过代理(proxy)机制来实现延迟加载的。它的具体过程是:Hibernate从数据库获取某一个对象数据时、获取某一个对象的集合属性值时,或获取一个对象所关联的另一个对象时,由于没有使用该对象的数据,Hibernate并不从数据库加载真正的数据,而只是为该对象创建一个代理对象来代表这个对象,真正从数据库中加载它的数据。这样在某些情况下,就可以提高查询效率。
有如下程序代码
Account acc=(Account)session
.load(Account.class,new Long(1));//返回的是一个代理对象
System.out.println(acc.getId());//没有发SQL语句到数据库加载数据
//创建真是的Account实例,并发送SQL语句到数据库中加载数据
System.out.println(acc.getLoginName());
Session的load()方法对实体的加载默认采用延迟加载(注:get()方法默认采用立即加载),所以以上第一行代码只返回一个代理对象,而第三行代码Hibernate才创建真是的Account实例,并发送SQL语句到数据库中加载数据。如果只访问对象标示符属性,它就没必要初始化代理。
在只需要一个Account类的一个引用时,这种延迟加载就很有用;例如:
Account acc=(Account)session.load(Account.class,new Long(1));
Order order=new order();
order.setCreatedTime(new Date());
order.setAccount(acc);
session.save(order);
在以上代码的第一行,它只是为Account类返回了一个代理对象,并没哟执行数据库查询。在这里也只需要Account的实例来创建一个新的Order(订单)对象,当调用session.save(order)时,将执行insert SQL语句,它也只需要Account的主标示符值作为外键报讯到订单表的对应字段中,这样就少执行一个数据库的select语句了,从而提高了查询效率。
Hibernate中默认采用延迟加载的情况主要有以下几种:
1、 当调用session上的load()方法加载一个实体时,会采用延迟加载。
2、 当Session加载某个实体时,会对这个实体中的集合属性值采用延迟加载。
3、 当Session加载某个实体时,会对这个实体所单端关联(one-to-one,many-to-one)的另一个实体对象采用延迟加载。
关闭延迟加载
延迟加载确实会给程序的查询效率带来好处,但有时明确知道数据需要立即加载的。如果Hibernate先默认使用延迟加载,而后又必须去数据库加载,反而会降低效率。所以,需要根据应用程序的实际情况来灵活控制是否使用延迟加载。在Hibernate中只需要修改相应的配置来启用或关闭延迟加载功能。
在加载单个实体,如果不需要延迟加载,就可以使用Session的get()方法。
当Session加载某个实体时,不需要对这个实体中的集合属性值延迟加载,而是要立即加载。这时可以在映射文件中针对这个集合的配置元素(<set>、<bag>、<list>……)添加属性lazy=false。
当Session加载某个实体时,不需要对这个实体所单端关联的另一个实体对象延迟加载,就可以在映射文件中针对这个单端关联的配置元素(<one-to-one>、<many-to-one>)添加属性lazy=false。
大对象的延迟加载
另外由于Hibernate针对实体类的普通属性(除标示符属性、集合属性和单端关联属性)默认都是采用立即加载,但在实体的某个属性类型为大对象(CLOB,BLOB)时,可能经常需要对这个属性进行延迟加载,此时可以在映射文件中针对这个属性的配置元素上添加属性lazy=true。
<?xml version=”1.0” encoding=”utf-8”?>
<!DOCTYPE hibernate-mapping PUBLIC
“-//Hibernate/Hibernate Mapping DTD 3.0//EN”
http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd>
<hibernate-mapping>
<!-- 映射持久化类 -- >
<class name=”com.qiujy.domian.Article” table=”article”>
<!-- 映射对象标示符 -- >
<id name=”id” column=”id” ><generator class=”native”/></id>
<!-- 映射普通属性 -- >
<property name=”title”/>
<property name=”author”/>
<!-- 对大对象类型的属性进行延迟加载.需要类增强器对本类的字节码文件进行强化处理,详见ant脚本文件build.xml -- >
<property name=”content” type=”clob” lazy=”true”>
<column name=”content” type=”longtext”/>
</property>
<property name=”createdtime” column=”created_time” type=”timestamp”/>
</class>
</hibernate-mapping>
另外还需要使用类增强器对二进制Xlass文件进行强化处理,Hibernate3中针对英语那那个程序中所使用的动态代理类库(cglib或javassist)提供对应的增强工具类,可以借助ANT来定义一个任务队需要进行强化处理的字节码文件进行强化处理。ANT脚本build.xml文件如下:
<?xml version=”1.0” encoding=”UTF-8”?>
<project basedir=”.” Default=”build” name=”hb_09_lazy”>
<!-- 指定第三方类库的存放位置 -- >
<property name=”lib.dir” value=”./3rdlibs”/>
<!-- 指定编译输出的目的地 -- >
<property name=”class.dir” value=”./bin”/>
<!-- 指定本应用所依赖的第三类库 -- >
<path id=”project.classpath”>
<fileset dir=”${lib.dir}”>
<include name=”**/*.jar”/>
</fileset>
</path>
<!-- 类增强工具ant任务 -- >
<target name=”instrument”>
<!-- 根据应用程序锁使用的而动态代理类库(cglib或javassist),选择对应的字节码增强工具类 -- >
<taskdef name=”instrument”
classname=”org.hibernate.tool.instrument.javassist.InstrumentTask”
classpathref=”project.classpath”/>
<!-- 指定要进行强化处理的类文件 -- >
<instrument verbose=”true”>
<fileset dir=”${classes.dir}/com.qiujy/domain”>
<include name=”Article.class”/>
</fileset>
</instrument>
</target>
<!-- 默认的任务bulid -- >
<target name=”build” depends=”instrument”/>
</project>
执行这个ANThou任务后,就可以对指定的字节码文件Article.class进行强化处理,之后就可以针对它的大对象属性content进行延迟加载。
抓取策略
通过抓取策略来直接影响Session()和get()或load()方法的查询效率。主要有以下两种情况。
1. 单端关联(<many-to-one>、<one-to-one>)上的抓取策略
可以给单端关联的映射元素添加fetch属性。Fetch属性有两个可选值。
Select:作为默认值,它的策略是当需要使用到关联对象的数据时,两位单独发送一条SELECTyuju 抓取当前对象的关联对象的数据。即加载延迟。
Join:它的策略是在同一条SELECT语句使用内连接来获得对象的数据和它的关联对象的数据,此时关联对象的延迟加载失效。
以下映射文件是单端关联上fetch=jion的一个配置示例。
<?xml version=”1.0” encoding=”utf-8”?>
<!DOCTYPE hibernate-mapping PUBIC
“-//Hibernate/Hibernate Mapping DTD 3.0//EN”
“http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd”>
<hibernate-mapping>
<!-- 映射持久化类 -- >
<class name=”com.qiujy.domain.Order” table=”order”>
<!-- 映射对象标示符 -- 》
<id name=”id” column=”id” ><generator class=”native”/></id>
<!-- 映射普通属性 -- >
<property name=”orderNo”/>
<property name=”createdtime” column=”created_time”type=” timestamp”/>
<!-- 映射多对一关联,添加fetch属性 -- >
<many-to-many name=”account” column=”account_id” fetch=”join” not-null=”true”/>
</class>
</hibernate-mapping>
在应用程序中加载某个Order实体数据时,会使用内连接把它关联的Account实体也加载上来,即Hibernate会产生如下一SQL语句。
Select
order0_.id as id-_1_,
order0_.orderNo as orderNo1_1_,
order0_.created_time as created3_1_1-,
order0_.account_id as account4_1_1_,
account1_.id as id0_0_,
account1_.login_name as login2_0_0_from
from
orders order0_
inner join
account account1_
on order_0.account_id=account1_.id
where
order0_.id=?
集合属性上的抓取策略
在集合属性的映射元素上可以添加fetch属性,它有三个可选值。
Select:作为默认值,它的策略是当需要使用所关联集合的数据时,另外单独发送一条SELECT语句使用内连接来获得对象的关联集合,此时关联集合上的lazy会失效。
Subselect:另外发送一条查询语句(或子查询)抓取在前面查询到的所有实体对象的关联集合。这个策略对HQL的查询也起作用。
以下映射文件就是集合属性上fetch=subselect的一个配置示例。
<?xml version=”1.0”?>
<!DOCTYPE hibernate-mapping PUBLIC
“-//Hibernate/Hibernate Mapping DTD 3.0//EN”
“http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd:>
<hibernate-mapping>
<!-- 映射持久化类 -- >
<class name=”com.qiujy.domain.Account” table=”account”>
<!—映射对象标示符 -- >
<id name=”id” column=”id”><generator class=”native”/></id>
<!—映射普通属性 -- >
<property name=”loginName” column=”login_name”/>
<! -- 用集合来映射一对多关联,并指定fetch属性-- >
<set name=”orderSet” cascade=”all” inverse=”true” fetch=”subselect”>
<key column=”account_id”/>
<one-to-many class=”com.qiujy.domain.Order:/>
</set>
</class>
</hibernate-mapping>
当使用get()或load()方法加载一个Account实体数据时,对它关联的Order集合属性先延迟加载,当真正需要使用Order集合属性中的数据时,才载发一条SQL语句来抓取数据;当使用HQL语句加载多个Account实体数据时,对它们关联的Order集合属性先延迟加载,当真正需要使用Order集合属性中的数据时,才会再发一条子查询语句来抓取相应的数据。
以上就是关于抓取策略的具体介绍,在实际项目开发过程中,不仅需要根据实际的情况选择合适的抓取策略,而且需要通过不断地测试来验证这个策略是不是最有效率的。