你的位置:首页 > Java教程

[Java教程]Hibernate Lazy属性与懒加载 整理


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.:

[html] view plain copy

 print?

  1. <set name="emps" inverse="true" lazy="false">  
  2.             <key column="depart_id" />  
  3.             <one-to-many class="Employee" />  
  4.         </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

 print?

  1. <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

 

 

比如说在college.hbm.

<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.