hibernate系列十二:延迟加载

来源:互联网 发布:鼻子丑怎么办 知乎 编辑:程序博客网 时间:2024/04/19 22:57

一.   简介

当Hibernate从数据库中加载DepartEntity对象时,如果同时自动加载所有关联的StudentEntity对象,而程序实际上仅仅需要访问DepartEntity对象,那么这些关联的StudentEntity对象就白白浪费了许多内存空间。本节以DepartEntity和StudentEntity类为例,介绍如何设置延迟加载,以优化查询性能。

    Hibernate查询DepartEntity对象时,立即查询并加载与之关联的StudentEntity对象,这种查询策略称为立即加载。立即加载存在两大不足:

    (1) select语句的数目太多,需要频繁地访问数据库,会影响查询性能。

    (2)在应用程序只需要访问DepartEntity对象,而不需要访问StudentEntity对象的场合,加载StudentEntity对象完全是多余的操作,这些多余的StudentEntity对象白白浪费了许多内存空间。

    为了解决以上问题,Hibernate提供了延迟加载策略。延迟加载策略能避免加载应用程序不需要访问的关联对象。

    Hibernate允许在对象一关系映射文件中配置加载策略。表1列出了用于设置加载策略的lazy属性。

类级别

<class>元素中Iazy属性的可选值为: true(延迟加载)和false(立即加载):

<class>元素中lazy属性的默认值为true一对多关联级别<set>元素中lazy属性的可选值为:true(延迟加载)、extra(增强延迟加载)和false(立即加载);<set>元素中lazy属性的默认值为true多对一关联级别<many-to-one>元素素中lazy属性的可选值为:proxy(延迟加载)、no-proxy(无代理延迟加载) 和 false(立即加载);  <many-to-one> 元素中lazy属性的默认值为proxy

二.   类级别的查询策略

类级别可选的加载策略包括立即加载和延迟加载,默认为延迟加载。如果<class>元素的lazy属性为true,表示采用延迟加载;如果lazy属性为false,表示采用立即加载。下面以DepartEntity.hbm.xml为例进行说明。

    1. 立即加载

    在DepartEntity.hbm.xml文件中,以下方式表示采用立即加载策略:

<classname="com.hopetech.entity.DepartEntity" table="Depart" lazy="false">

当通过Session的load()方法加载DepartEntity对象时:

    DepartEntity  dept=(DepartEntity)session.load(DepartEntity.calss,121); 

Hibernate会立即执行查询Depart表的select语句:

    select  *  from  Depart  where deptId=?

    2.延迟加载

    类级别的默认加载策略是延迟加载。在DepartEntity.hbm.xml文件中,以下两种方式都表示采用延迟加载策略:

<classname="com.hopetech.entity.DepartEntity" table="Depart" lazy="true">

<classname="com.hopetech.entity.DepartEntity" table="Depart">

 

  如果程序加载一个持久化对象的目的是为了访问它的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是为了获得它的引用,则可以采用延迟加载,无须访问DepartEntity对象的属性,如示例1所示。

示例l

DepartEntity  dept=(DepartEntity)session.load(DepartEntity.calss,121);

StudentEntity   student=new StudentEntity(1101, "Tom");

student.setDept(dept);

session.save(student);

 

示例l6向数据库保存了一个StudentEntity对象,它与已经存在的一个DepartEntity持久化对象关联。如果在DepartEntity类级别采用延迟加载,则session.load()方法不会执行访问Depart表的select语句,只返回一个DepartEntity代理类的对象,它的departId属性为121,其余属性都为NULL。session.save()方法执行的SQL insert语句:

insertinto Student(stuId,stuName,…,deptId) values(1101,’Tom’,…,121)

 

    当<class>元素的1azy属性为true时,会影响Session的load()方法的各种运行时行为,下面举例说明。

    (1)如果加载的DepartEntity对象在数据库中不存在,Session的load()方法不会抛出异常,只有当运行dept.getDepartName()方法时才会抛出以下异常:

    org.hibernate.ObjectNotFoundException:Norow with the given identifier exists

    (2)如果在整个Session范围内,应用程序没有访问过DepartEntity对象,那么DepartEntity代理类的实例一直不会被初始化,Hibernate不会执行任何select语句。以下代码试图在关闭Session后访问DepartEntity游离对象:

DepartEntity   dept=(DepartEntity)session.load(DepartEntity.calss,121);

session.close();

dept.getDeptName();

由于变量dept引用的DepartEntity代理类的实例在Session范围内始终没有被初始化,因此执行dept.getDeptName()方法时,Hibernate会抛出以下异常:

org.hibernate.LazyInitializationException:couldnot initialize proxy-no Session

由此可见,DepartEntity代理类的实例只有在当前Session范围内才能被初始化。

(3)org.hibernate.Hibernate类的initialize()静态方法用于在Session范围内显式初始化代理类实islnitialized()方法用于判断代理类实例是否已经被初始化。例如:

DepartEntity   dept=(DepartEntity)session.load(DepartEntity.calss,121);

if(!Hibernate.isInitialized(dept))

    Hibernate.initialize(dept);

session.close();

dept.getDeptName();

    以上代码在Session范围内通过Hibernate类的initialize()方法显式初始化了DepartEntity代理类实例,因此Session关闭后,可以正常访问DepartEntity游离对象。

    (4)当应用程序访问代理类实例的getDeptId()方法时,不会触发Hibernate初始化代理类实例的行为,例如:

DepartEntity  dept=(DepartEntity)session.load(DepartEntity.calss,121);

dept.getDeptId();

session.close();

dept.getDeptName();

 

当应用程序访问dept.getDeptId()方法时,该方法直接返回DepartEntity代理类实例的OID值,无须查询数据库。由于变量dept始终引用的是没有被初始化的DepartEntity代理类实例,因此当Session关闭后再执行dept.getDeptName()方法,Hibernate会抛出以下异常:

org.hibernate.LazyInitializationException:couldnot initialize proxy-no Session

    值得注意的是,不管DepartEntity.hbm.xml文件的<class>元素的Lazy属性是true还是false,Session的get()方法及Query对象的list()方法在DepartEntity类级别总是使用立即加载策略,举例说明如下:

(1) Session的get()方法总是立即到数据库中查询DepartEntity对象,如果在数据库中不存在相应的数据,就返回NULL。例如:

DepartEntity   dept=(DepartEntity)session.get(DepartEntity.calss,121);

当通过Session的get()方法加载DepartEntity对象时,Hibernate会立即执行以下select语句:

   select * from Depart where deptId=?

    如果存在相关的数据,get()方法就返回DepartEntity对象,否则就返回NULL。get()方法永远不会返回DepartEntity代理类实例,这是与load()方法的又一个不同之处。

(2) Query对象的list()方法总是立即到数据库中查询DepartEntity对象,例如:

List  deptList=session.createQuery(“from DepartEntity”).list();

当运行Query对象的list()方法时,Hibernate立即执行以下select语句:

    select * from Depart


三   一对多关联的查询策略

推荐默认按照延迟加载策略进行。

在映射文件中,用<set>元素来配置一对多关联及多对多关联关系的加载策略。DepartEntity.hbm.xml文件中的以下代码用于配置DepartEntity和StudentEntity类的一对多关联关系:

<set  name="students" inverse="true" lazy="true">

     <key  column="deptId"></key>

     <one-to-many   class="com.hopetech.entity.StudentEntity"></one-to-many>

</set>

<set>元素有lazy属性,主要决定students集合被初始化的时机,即到底是在加载DepartEntity对象时就被初始化,还是在程序访问students集合时被初始化。表2描述了<set>元素的lazy属性取不同值时设置的查询策略。

lazy属性

(默认值为true)

加载策略

true延迟加载false立即加载extra加强延迟加载

1.立即加载

以下代码表明DepartEntity类的students集合采用立即加载策略:

<set  name="students" inverse="true" lazy="false">…</set>

 

示例17通过Session的get()方法加载OID为121的DepartEntity对象。

示例17

DepartEntity   dept=(DepartEntity)session.get(DepartEntity.calss,121);

 

 执行Session的get()方法时,对于DepartEntity对象采用类级别的立即加载策略;对于DepartEntity对象的Students集合(即与DepartEntity关联的所有StudentEntity对象),采用一对多关联级别的立即加载策略。因此Hibernate执行以下select语句:

select  * from Depart where  dept0_.deptId=?

select  * from Student where  deptId=?

    通过以上select语句,Hibernate加载了一个DepartEntity对象和多个StudentEntity对象。但在很多情况下,应用程序并不需要访问这些StudentEntity对象,加载StudentEntity对象显然是多余的,所以在一对多关联级别中不能随意使用立即加载策略。

2.延迟加载

对于<set>元素,应该优先考虑使用默认的延迟加载策略:

<set   name="students" inverse="true">…</set>

 

或者

<set   name="students"  inverse="true" lazy="true">…</set>

 

运行示例17中Session的get(DepartEntity.class,121)方法时,仅立即加载DepartEntity对象,执行以下select语句:

select   *  from Depart  where  dept0_.deptId=?

    Session的get()方法返回的DepartEntity对象的students属性引用一个没有被初始化的集合代理类实例,换句话说,此时students集合中没有存放任何StudentEntity对象。只有当students集合代理类实例被初始化时,才会到数据库中查询所有与DepartEntity关联的所有StudentEntity对象,执行以下select语句:

select   *   from   Student   where deptId=?

那么,DepartEntity对象的students属性引用的集合代理类实例何时初始化呢?主要包括以下两种情况:

(1)当应用程序第一次访问它时,如调用它的iterator()、size()、isEmpty()或contains()方法:

Set<StudentEntity>   students=dept.getStudents();

//以下语句导致students集合代理类实例被初始化

   Iterator<StudentEntity> stuIterator= students.iterator();

(2)通过org.hibernate.Hibernate类的initialize()静态方法初始化它:

   Set<StudentEntity>   students=dept.getStudents();

  //以下语句导致students集合代理类实例被初始化

Hibernate.initialize(students);

 3.增强延迟加载

    在<seT元素中配置lazy属性为“extra”。以下代码表明采用增强延迟加载策略:

   <set name="students"   inverse="true" lazy="extra">…</set>

    增强延迟加载策略与一般的延迟加载策略(lazy=”true”)很相似。主要区别在于,增强延迟加载策略能进一步延迟DepartEntity对象的students集合代理类实例的初始化时机。当程序第一次访问students属性的iterator()方法时,会导致students集合代理类实例的初始化,而当程序第一次访问students属性的size()、contains()和isEmpty()方法时,Hibernate不会初始化students集合代理实例,仅通过特定的select语句查询必要的信息。以下程序代码演示了采用增强延迟加载策略时的Hibernate运行时行为:

DepartEntity   dept=(DepartEntity)session.get(DepartEntity.calss,121);

    //以下语句不会初始化students集合代理类实例

    //执行SQL语句:select   count(stuId)    from student where deptId=?

    dept.getStudents().size();

   

    //以下语句会初始化students集合代理类实例

    //执行SQL语句:select* from student where deptId=?

    dept.getStudents.iterator();


四   多对一关联的查询策略

在映射文件中,<many-to-one>元素用来设置多对一关联关系。在StudentEntity.hbm.xml文件中,以下代码设置StudentEntity类与DepartEntity类的多对一关联关系。

<many-to-one

      name="dept" 

      column="deptId"

      lazy="proxy"

     class= "com.hopetech.entity.DepartEntity"/>

 

<many-to-one>元素的lazy属性取不同值时设置的加载策略,如表3所示。

lazy属性

(默认值为proxy)加载策略proxy延迟加载no-proxy无代理延迟加载False立即加载

如果没有显式设置lazy属性,那么在多对一关联级别采用默认的延迟加载策略。假如应用程序仅仅希望访问StudentEntity对象,并不需要立即访问与StudentEntity关联的DepartEntity对象,则应该使用默认的延迟加载策略。

  1.延迟加载

    在<many-to-one>元素中配置lazy属性为“proxy”,延迟加载与StudentEntity关联的DepartEntity对象,如示例l8所示。

    示例18

StudentEntity  student=(StudentEntity)sesion.get(StudentEntity.class,1104)

//student.getDept()返回DepartEntity代理类实例的引用

DepartEntity  dept=student.getDept();

dept.getDeptName();

当运行Session的get()方法时,仅立即执行查询StudentEntity对象的select语句:

    select * from Student where stuId=?

    StudentEntity对象的dept属性引用DepartEntity代理类实例,这个代理类实例的OID由Student表的deptId外键值决定。当执行dept.getDeptName()方法时,Hibernate初始化DepartEntity代理类实例,执行以下select语句到数据库中加载DepartEntity对象:

    select  *  from Depart where deptId=?

2.无代理延迟加载

在<many-to-one>元素中配置lazy属性为“no-proxy”。对于以下程序代码:

//第1行

StudentEntitystu  dent=(StudentEntity)sesion.get(StudentEntity.class,1104)

DepartEntity  dept=student.getDept();//第2行

dept.getDeptName();//第3行

 

    如果对StudentEntity对象的dept属性使用无代理延迟加载,即<many-to-one>元素的lazy属性为no-proxy,那么程序第1行加载的StudentEntity对象的dept属性为NULL。当程序第2行调用student.getDept()方法时,将触发Hibernate执行查询Depart表的select语句,从而加载DepartEntity对象。

由此可见,当lazy属性为proxy时,可以延长延迟加载DepartEntity对象的时间。而当lazy属性为no-proxy时,则可以避免使用由Hibernate提供的DepartEntity代理类实例,使Hibernate对程序提供更加透明的持久化服务。

 

注意      当lazv属性为no-proxy时,需要在编译时使用工具增强持久化类的字节码,否则运行情况和lazy属性为proxy时相同。增强持久化类的详细配置请查看Hibernate帮助文档。

 

 3.立即加载

在<many-to-one>元素中配置lazy属性为“false”。以下代码把StudentEntity.hbm.xml文件的<many-to-one>元素的lazy属性设为false:

<many-to-one

      name="dept" 

      column="deptId"

      lazy="false"

      class="com.hopetech.entity.DepartEntity"/>

对于以下程序代码:

StudentEntity   student=(StudentEntity)sesion.get(StudentEntity.class,1104)

在运行session.get()方法时,Hibernate执行以下select语句:

select  *   from Student  where stuId=?

select   *   from Depart  where deptId=?







原创粉丝点击