Hibernate 延迟加载(一)

1 延迟加载策略

  Hibernate 的延迟加载(lazy load)是一个被广泛使用的技术。这种延迟加载保证了应用只有在需要时才去数据库中抓取相应的记录通过延迟加载技术可以避免过多、过早地加载数据表里的数据,从而降低应用的内存开销。

Hibernate 的延迟加载本质上就是代理模式的应用,当程序通过 Hibernate 装载一个实体时,默认情况下,Hibernate 并不会立即抓取它的集合属性、关联实体所以对应的记录,而是通过生成一个代理来表示这些集合属性、关联实体,这就是代理模式应用带来的优势。

  但是,延迟加载也是项目开发中特别常见的一个错误。如果对一个类或者集合配置了延迟检索策略,那么必须当代理类实例或代理集合处于持久化状态(即处于Session范围内)时,才能初始化它。如果在游离状态时才初始化它,就会产生延迟初始化错误。所以,在开发独立的DAO数据访问层时应该格外小心这个问题。

  如果在获取对象的时候使用的是session.get()是不会延迟加载的,只有在使用load、hql时候才会延迟加载。

      Hibernate中允许使用延迟加载的地方主要有以下几个地方:

<hibernate-mapping default-lazy=(true|false)”true”>:设置全局的延迟加载策略。

<class lazy=(true|false)>:DTD没设置默认值,推理默认值为true

<property lazy=(true|false)>:设置字段延迟加载,默认为false

<component lazy=(true|false):默认为false

<subclass lazy=(true|false)>:默认设置为true

<join-subclass lazy=(true|false)>:默认设置为true

<union-subclass lazy=(true|false)>:默认设置为true

<many-to-one lazy=(proxy|no-proxy|false)>:默认为proxy

<one-to-one lazy=(proxy|no-proxy|false)>:默认为proxy

<map lazy=(true|extra|false)>:默认为true

<set lazy=(true|extra|false)>:默认为true

<bag lazy=(true|extra|false)>:默认为true

<ibag lazy=(true|extra|false)>:默认为true

<list lazy=(true|extra|false)>:默认为true


2 对象加载<class>

2.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如下:

11104312-77430cb248e647f1b4ba9377bfbf481

图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=?
复制代码

  这时候,我们查看内存里面的对象如下:

11104449-1954ccae56e148d5b90bad85a13fc4e

图2.2 class延迟加载时内存对象

  真正的Person对象放在CGLIB$CALLBACK_0对象中的target属性里。

  这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。

2.2 非延迟加载策略

  Hibernate默认的策略便是非延迟加载的,所以设置lazy=false


1     tx = session.beginTransaction();2     Pe

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=?
复制代码

  我们在查看内存快照如下:

11104806-f36d698286c94c45abbbb6a8d074c38

     这个时候就不是一个代理类了,而是Person对象本身了。里面的属性也已经全部普通属性也全部被加载。这里说普通属性是因为addresses这个集合对象并没有被加载,因为set自己本身也可以设置lazy属性。所以,这里也反映出class对象的lazy并不能控制关联或集合的加载策略。(普通属性会被加载,但是集合之类的就备受控制)

2.3 总结

  Hibernate中<class lazy=””>默认为true。如果,在load的时候只会返回一个代理类,并不会正在从数据库中读取数据。第一次用到时,会将所有普通属性(set这种就不是)全部加载进来。如果第一次使用到时,session已经关闭将发生错误。

  如果显式是设置lazy=false,load的时候即会把所有普通属性全部读取进来。而且,返回的将是一个真正的该类型的对象(如Person),而不是代理类。

3 字段加载(property)

  在Hibernate3中,引入了一种新的特性――属性的延迟加载,这个机制又为获取高性能查询提供了有力的工具。在大数据对象读取时,如Person对象中有一个School字段,该字段是一个java.sql.Clob类型,包含了用户的简历信息,当我们加载该对象时,我们不得不每一次都要加载这个字段,而不论我们是否真的需要它,而且这种大数据对象的读取本身会带来很大的性能开销。



你可能感兴趣的:(Hibernate延迟加载)