lazy概念:要用到的时候,再去加载,对于关联的集合来说,只有当访问到的时候,才去加载它所关联的集合,比如一个user对应很多权限,只有当user.getRights()的时候,才发出select right的语句,在访问到rights之前,rights是一个PersisitSet对于实体类来说,只有当它的属性被访问到时,才会真正加载这个实体类,在它的属性没有被访问到之前,这个实体类是一个代理对象。
1.在集合中定义:<set><list>标签上
,可以取值:true/false/extra
<set name="name" lazy="true/false/extra" >
默认为true
默认为true情况下,当使用到了Set对象,才会把整个set全部查询出来。
false情况下,不使用Lazy,查询Lazy所属的对象时,set就会被查询上来。extra情况下,比较智能,根据查询的内容,生成不同的SQL语句。效率会高一些。
例子:在我们前边多对一的关系中(部门与员工):
Department.hbm.xml:
[html] view plain copy
- <set name="emps" inverse="true" lazy="false">
- <key column="depart_id" />
- <one-to-many class="Employee" />
- </set>
通过这个可以关闭默认的懒加载
2单端关联 <one-to-one><many-to-one>单端关联上,可以取值:false/proxy/no-proxy
<many-to-one name="name" lazy="false/proxy/no-proxy">
默认为proxy
false:不使用Lazy。此关联总是被预先抓取
proxy:使用懒加载
no-proxy:指定此属性应该在实例变量第一次被访问时应该延迟抓取(fetche lazily)
[html] view plain copy
- <many-to-one name="depart" column="depart_id" lazy="false"/>
- lazy="proxy" applies to single objects (ie foo.SingleBar)
- lazy="true" applies to collections of objects (ie foo.MultiBar)
(You can't set lazy="proxy" to a collection, nor can you set lazy="true" to a single reference. Either will cause NH to throw a XmlSchemaException which is a little cryptic to beginners.)
比如说在college.hbm.xml里面写上
<set name="majors" inverse="true" lazy="false" cascade="delete">
加载学院时立刻加载学院的专业,那么在查询所有学院时,产生的sql语句如下:
Hibernate: select majors0_.college_id as college_2_3_0_, majors0_.major_id as major_id1_6_0_, majors0_.major_id as major_id1_6_1_, majors0_.college_id as college_2_6_1_, majors0_.major_name as major_na3_6_1_, majors0_.major_code as major_co4_6_1_ from studorm.tb_major majors0_ where majors0_.college_id=?
在查询所有学院的时候,会立刻加载每个学院的专业,所以如非必要,不要加上
十分浪费资源
以下来自:http://www.cnblogs.com/wukenaihe/archive/2013/06/11/3131640.html
3.class标签
除了用在<set> 和 <one-to-one><many-to-one>标签上,lazy还能用在
* <class>标签上,可以取值:true/false ,在hibernate3以上版本,默认是true
* <property>标签上,可以取值:true/false
在<class>标签上,可以取值:true/false ,在hibernate3以上版本,默认是true
默认为true,可不写,在执行查询语句时不进行,比如session.load(id)时,不执行sql语句(session.get(id)不支持lazy),而是在具体获取参数时,执行sql语句,比如obj.getName()。
<class>标签上的lazy特性只对普通属性起作用
<class>标签上的lazy不会影响到单端关联上的lazy特性
3.1 延迟加载策略(默认)
如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置
<class name="Person" table="PERSON" lazy="true">
1 tx = session.beginTransaction();
2 Person p=(Person) session.load(Person.class, "001");//(1)
3 System.out.println("0: "+p.getPersonId());//(2)
4 System.out.println("0: "+p.getName());//(3)
5 tx.commit();
6 session.close();
执行到(1)并没有出现sql语句,并没有从数据库中抓取数据。这个时候查看内存对象p如下:
图2.1 person对象load时的内存快照
观察person对象,我们可发现是Person$$EnhancerBy..的类型的对象。这里所返回的对象类型就是Person对象的代理对象,在hibernate中通过使用CGLB来先动态构造一个目标对象的代理类对象,并且在代理对象中包含目标对象的所有属性和方法。所以,对于客户端而言是否为代理类是无关紧要的,对他来说是透明的。这个对象中,仅仅设置了id属性(即personId的值),这是为了便于后面根据这个Id从数据库中来获取数据。
运行到(2)处,输出为001,但是仍然没有从数据库里面读取数据。这个时候代理类的作用就体现出来了,客户端觉得person类已经实现了(事实上并未创建)。但是,如果这个会后session关闭,再使用person对象就会出错了。
调试运行到(3)处,要用到name属性,但是这个值在数据库中。所以hibernate从数据库里面抓取了数据,sql语句如下所示:
Hibernate:
select
person0_.PERSONID as PERSONID3_0_,
person0_.NAME as NAME3_0_
from
PERSON person0_
where
person0_.PERSONID=?
这时候,我们查看内存里面的对象如下:
图2.2 class延迟加载时内存对象
真正的Person对象放在CGLIB$CALLBACK_0对象中的target属性里。
这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。
3.2 非延迟加载策略
Hibernate默认的策略便是非延迟加载的,所以设置lazy=false
1 tx = session.beginTransaction();
2 Person p=(Person) session.load(Person.class, "001");//(1)
3 System.out.println("0: "+p.getPersonId());//(2)
4 System.out.println("0: "+p.getName());//(3)
5 tx.commit();
6 session.close();
调试运行到(1)处时,hibernate直接执行如下sql语句:
Hibernate:
select
person0_.PERSONID as PERSONID3_0_,
person0_.NAME as NAME3_0_
from
PERSON person0_
where
person0_.PERSONID=?
我们在查看内存快照如下:
这个时候就不是一个代理类了,而是Person对象本身了。里面的属性也已经全部普通属性也全部被加载。这里说普通属性是因为addresses这个集合对象并没有被加载,因为set自己本身也可以设置lazy属性。所以,这里也反映出class对象的lazy并不能控制关联或集合的加载策略。
2.3 总结
Hibernate中<class lazy="">默认为true。如果,在load的时候只会返回一个代理类,并不会正在从数据库中读取数据。第一次用到时,会将所有普通属性(set这种就不是)全部加载进来。如果第一次使用到时,session已经关闭将发生错误。
如果显式是设置lazy=false,load的时候即会把所有普通属性全部读取进来。而且,返回的将是一个真正的该类型的对象(如Person),而不是代理类。
4字段加载(property)
在Hibernate3中,引入了一种新的特性——属性的延迟加载,这个机制又为获取高性能查询提供了有力的工具。在大数据对象读取时,如Person对象中有一个School字段,该字段是一个java.sql.Clob类型,包含了用户的简历信息,当我们加载该对象时,我们不得不每一次都要加载这个字段,而不论我们是否真的需要它,而且这种大数据对象的读取本身会带来很大的性能开销。
1、 <class lazy="false">
配置如下
1 tx = session.beginTransaction();
2 Person p=(Person) session.load(Person.class, "001");//(1)
3 System.out.println("");//(2)
4 System.out.println("0: "+p.getPersonId());//(3)
5 System.out.println("0: "+p.getName());//(4)
6 System.out.println("0: "+p.getSchool());//(5)
7 tx.commit();
1 <property name="name" type="java.lang.String">
2 <column name="NAME" />
3 </property>
4 <property name="school" type="java.lang.String" lazy="true">
5 <column name="SCHOOL"></column>
6 </property>
当运行到1的时候,全部加载了,执行语句如下:
Hibernate:
select
person0_.PERSONID as PERSONID3_0_,
person0_.NAME as NAME3_0_,
person0_.SCHOOL as SCHOOL3_0_
from
PERSON person0_
where
person0_.PERSONID=?
所有普通属性都均已加载。
2、<class lazy="true">
School的lazy属性自然还是true。当程序运行到(4)时,也同样加载了全部属性,执行了如下sql:
Hibernate:
select
person0_.PERSONID as PERSONID3_0_,
person0_.NAME as NAME3_0_,
person0_.SCHOOL as SCHOOL3_0_
from
PERSON person0_
where
person0_.PERSONID=?
结果就是无效,不管采用何种策略都是无效的,和我们想想的有较大出路。下面是一段来自hibernate官方文档的话。
Lazy property loading requires buildtime bytecode instrumentation. If your persistent classes are not enhanced, Hibernate will ignore lazy property settings and return to immediate fetching.
应该是因为,我们并未用到编译时字节码增强技术的原因。如果只对部分property进行延迟加载的话,hibernate还提供了另外的方式,也是更为推荐的方式,即HQL或者条件查询。
A different way of avoiding unnecessary column reads, at least for read-only transactions, is to use the projection features of HQL or Criteria queries. This avoids the need for buildtime bytecode processing and is certainly a preferred solution.
实例
User和rights是双向一对多,一个user有多条权限,在user.hbm.xml里这么配置
<!-- 权限和用户的关系由权限来掌控,采用懒加载 -->
<set name="rights" inverse="true" cascade="delete" >
<key>
<column name="user_rowcount" not-null="true" />
</key>
<one-to-many class="com.studorm.entity.Right" />
</set>
当通过user获取他的所有权限的时候,可以这么写:
Session session=getCurrentSession();
User user=(User)session.get(User.class, userRowcount);
Set<Right> rights=(Set<Right>)user.getRights();
//注意user.getRights()返回PersisitSet是set的子类,不是HashSet的子类.HashSet<Right> rights=(HashSet<Right>)user.getRights()抛异常
System.out.print("获取用户 "+user.getLoginId()+"的权限有"+rights.size()+"条: ");
Iterator<Right> iter = rights.iterator();
while (iter.hasNext())
{
Right right= it.next();
}
产生的sql语句:
Hibernate: select user0_.user_rowcount as user_row1_9_0_, user0_.manage_college_id as manage_c2_9_0_, user0_.login_id as login_id3_9_0_, user0_.login_pwd as login_pw4_9_0_, user0_.user_type as user_typ5_9_0_, user0_.user_phone as user_pho6_9_0_ from studorm.tb_user user0_ where user0_.user_rowcount=?
Hibernate: select rights0_.user_rowcount as user_row2_9_0_, rights0_.right_rowcount as right_ro1_7_0_, rights0_.right_rowcount as right_ro1_7_1_, rights0_.user_rowcount as user_row2_7_1_, rights0_.function_id as function3_7_1_ from studorm.tb_right rights0_ where rights0_.user_rowcount=?