Hibernate延迟加载你真正会了吗?
之所以用Hibernate框架,肯定是有化点。Hibernate上手容易,用好难。就拿我们最近一个项目来说吧,其中有两个配置文件中lazy设成false,这也许是同事为了不让报初始化异常而改的。当时信息量不是很大,看不出什么大的问题。但是最这段时间,客户打电话过来说经常down机, 前几天过去帮他们做了一下性能测试,发没这个罪魁祸首就是这个lazy="false",还有一个就是属性延迟加载没有起作用。当是时以为将属性节点中的lazy="true"就可以啦,后来一看官方文档,还要对class进行字节码增强。
下面就来具体探讨一下实体对象延迟加载,属性延迟加载,单值延迟加载,集全延迟加载四个方面
实体对象延迟加载
其实就是只类延迟加载
如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置,如下所示:
其中注意
<property name="lib.dir" value="./lib"/>所需的JAR文件路径
property name="classes.dir" value="./classes"/>编译输出路径
单值延迟加载
也就是many-to-many one-to-one many-to-one,延迟加载是默认,默认值是产生一个代理对象,结合抓取策略进行性能调优。下面看看fetch的作用及用法。
Fetching strategies(取策略)
Fetching stategies是指hibernate在需要关联数据的时候所采用的取关联数据的策略。这个策略既可以在O/R映射文件里配,也可以通过特殊的HQL:或Criteria语句实现。
Hibernate定义了以下取策略:
Join fetching : Hibernate取关联数据或集合是通过OUTER JOIN的方式,通过同一条select 语句来实现。
Select fetching:在没有指定了lazy = "false"(既延迟加载有效)的情况下,通过另一条select 语句来获得与已经获得的实体相关的实体或集合。当然
这种情况发生在用户真正要获得关联对象的时候。
Subselect fetching:在没有指定了lazy = "false"(既延迟加载有效)的情况下,先通过一条查询语句获得了一个实体集, 然后对这个实体集中的每一个对象通过另一条select 语句来获得与它相关的实体或集合。
当然这种情况发生在用户真正要获得关联对象的时候。
Batch fetching :它是为查询数据提供的一种优化策略。通过指定主键或外键的列表的方式来实现一条select 语句获得一批实体或集合。
从另一个角度来看,hibernate的fetching 分成以下几种。
Immediate fetching: 如果实体已经被加载了,他的关联对象,关联集合,属性也要及时加载。
lazy collection fetching: 只有应用程序真正使用这个集合的时候,才加栽这个集合。
"Extra-lazy" collection fetching : hibernate 不加载一个集合的所有对象到内存里,需要哪个个体,加载哪个。
Proxy fetching :当前对象的单值相关对象只有在调用它的主键外的其他属性的get方法时才加载它。
"NO-proxy"fetching :当前对象的单值相关对象在它的实体变量被访问的时候就被加载。相对于Proxy fetching来说,Proxy fetching更延迟。
(因为"NO-proxy"fetching即使是访问关联对象的主健,关联对象都要被加载)。"NO-proxy"fetching对于应用来说更条理清晰。因为在应用中没有一个可见的proxy.
个人认为可以这样理解上述情况,假如在数据库中存在两张表 A,B.表A中有一个指向表B主健的外键。如果想知道A表中的某条
数据对应B表中的那条记录的主键。完全不用访问B表,A表中的此条数据的外键值就是B表中对应数据的主键。所有只有访问B表中
对应数据的主键外其他属性时,才需要加载B表中的这条数据。
Lazy attribute fetching :当前对象的某个属性或单值相关对象只有在与它对应的实体变量被访问的时候才加载。
Working with lazy associations
默认的情况下,Hibernate3 在获取关联对象集合的时候使用的是lazy策略,获得单值关联对象的时候使用的是lazy proxy策略。这样的策略
几乎适用所有的应用。
如果你设置了hibernate.default_batch_fetch_size,Hibernate就会通过批量获取来优化lazy fetching.
lazy fetching 会引起一个问题。就是关闭了hibernate session以后加载延迟加载的对象。这样会引起异常。如下:
在映射文件里定义的取策略会影响如下操作:
由 get() 或load()执行的取操作。
操作关联对象而引发的取操作。
Criteria查询。
如果使用了subselect 这种取策略还会影响HQL这种查询方式。
一般来说,我们不是通过在映射配置文件自定义取策略,而是通过在一个事务里,通过在特定的HQL里使用 left join 来覆盖默认的取
策略。对于Criteria 来说,提供了setFetchMode(FetchMode.JOIN) API.如下:
首先要注意的是,Cat的实例不能当作DomesticCat实例使用。即使Cat和DomesticCat对应的是同一条数据。
CatImpl实现了接口Cat,DomesticCatImpl实现了接口DomesticCat.Cat和DomesticCat实例的代理可以
被load()或iterator()方法返回.(list()方法一般不返回代理).
当然Collection filter也可以获取集合的一部分数据
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
Using batch fetching
批量获取数据可以提高Hibernate的效率.批量获取是延迟select fetching策略的一种优化.我们可以对类或者集合两个角度采用批量取数据.
批量获取类/实体容易理解,假设有如下情况:
在你的session里加载了25个Cat实例。每一个Cat都有一个own的引用指向一个person.在这里这个关联的person是通过代理的方式延迟加载
(单值关联对象)。如果你现在要通过循环调用所有cat的getOwner()方法。hibernate会默认的执行25个select 语句来获得被代理的owner对象。
我们可以通过在Person这个表的映射文件中指定batch-size来实现批量取数据。
<class name="Person" batch-size="10">...</class>
Hibernate 现在会执行三条查询语句来完成查询,模式是10,10,5.
你也可以对集合进行批量取操作.例如,每一个person都有一个被延迟加载的集合Cats.现在在session中已经加载了10个 person实例.循环调用
所有的person的getCats()方法会产生10条select 语句.如果你在person的映射文件中定义了批量获取模式:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
通过设置batch-size设为3,Hibernate 会以3,3,3,1的模式通过四条select语句加载集合。
Using subselect fetching
如果要加载一个延迟加载的集合或一个单值的代理,Hibernate通过一个subselect 运行原来的查询语句,这种情况和batch-fetching是异曲同工的。
Using lazy property fetching
Hibernate支持对单个属性的延迟加载。这个优化技术也被 称为fetch groups. 需要注意的是,这个技术还处于推销阶段。因为在实际中,对行的读取
优化比对列的优化更重要。然而在一些特殊情况下,加载一个类的部分属性还是有必要的,比如一个继承的表有几百列而且数据模型还不能改变。
为了使某个属性被延迟加载,只需要在这个属性的影射文件中加上lazy属性即可。
属性的延迟加载需要使用运行时的字节设备来处理。如果你的持久化类还没有被这个设备处理。hibernate 会忽略这个设置
采用及时加载的方式。
要想使用此字节设备处理持久化类,使用如下的Ant 任务。
另一种避免加载不需要的列的方式,至少在只读事务中,是通过使用HQL或Criteria查询属性。这样可以避免使用字节
处理工具。
你可以通过在HQL指定fetch all properties 来加载全部属性。
集合延迟加载
在Hibernate的延迟加载机制中,针对集合类型的应用,意义是最为重大的,因为这有可能使性能得到大幅度的提高,为此Hibernate进行了大量的努力,其中包括对JDK Collection的独立实现,我们在一对多关联中,定义的用来容纳关联对象的Set集合,并不是java.util.Set类型或其子类型,而是 net.sf.hibernate.collection.Set类型,通过使用自定义集合类的实现,Hibernate实现了集合类型的延迟加载。为了对集合类型使用延迟加载,我们必须如下配置我们的实体类的关于关联的部分:<hibernate-mapping> <class name=”com.neusoft.entity.User” table=”user”>…..<set name=”addresses” table=”address” lazy=”true” inverse=”true”><cache usage=”read-write”/><key column=”user_id”/><one-to-many class=”com.neusoft.entity.Arrderss”/></set> </class></hibernate-mapping>
此时Hibernate会对集合类型中的实体也进行缓存,如果根据这个配置再次运行上面的代码,将会得到类似如下的输出:
Select * from user where id=’1’;
Select * from address where user_id=’1’;
Tianjin
Dalian
Second query……
Tianjin
Dalian
这时将不会再有根据数据索引进行查询的SQL语句,因为此时可以直接从缓存中获得集合类型中存放的实体对象。