lazy延迟加载

lazy(延迟加载)策略可用于<class>标签,<property>标签,集合(<set>/<list>)标签以及<one-to-one>/<many-to-one>标签上

<class>

class标签中的lazy可选属性为true/false,默认为ture,代表默认使用延迟加载策略

 1 public static void main(String[] args) {
 2         Session session = HibernateFactory.currentSession();
 3         Transaction tx = session.beginTransaction();
 4         
 5         Student student = (Student) session.load(Student.class, 1);
 6         
 7         tx.commit();
 8         HibernateFactory.closeSession();
 9         
10         System.out.println(student.getName());
11     }

以上代码在Session范围内load了一个Student对象,此时Hibernate不会立即执行查询student表的select语句,仅仅返回Student类的CGLIB代理类的实例,这个代理类实例有以下特征:

1:由Hibernate在运行时动态生成,继承了Student类的所有属性和方法。

2:当Hibernate创建Student代理类实例时,仅仅初始化了它的OID属性,其他属性都为null。

3:当应用程序第一次访问Student代理类实例时(例如调用student.getXXX()或student.setXXX()方法),Hibernate会初始化代理类实例,执行select语句,从数据库中加载Student对象的所有数据。但应用程序访问Student代理类实例的getId()方法时,Hibernate不会初始化代理类实例,因为在创建代理类实例时OID就存在了,不必到数据库中去查询。 

4:如果在Session范围内没有访问Student代理类实例,而是在Session关闭后访问了代理类实例,那么就会抛出"could not initialize proxy - no Session"的异常。

 

所以上诉代码会报错,如果想要代码正常运行,有以下四种修改方法

1:将<class>中lazy属性设置未false,表示不使用延迟加载策略,当Session调用load()函数时会立刻从数据库中select出student的数据

2:在Session范围内访问一下student对象(例如调用student.getName())

3:使用get()方法代替load()方法,get()方法执行的时候会立即向数据库发出查询语句

4:使用Hibernate的API initialize来初始化代理类实例,代码如下

 1 public static void main(String[] args) {
 2         Session session = HibernateFactory.currentSession();
 3         Transaction tx = session.beginTransaction();
 4         
 5         Student student = (Student) session.load(Student.class, 1);
 6         if(!Hibernate.isInitialized(student)) {
 7             Hibernate.initialize(student);
 8         }
 9     
10         tx.commit();
11         HibernateFactory.closeSession();
12         
13         System.out.println(student.getName());
14     }

 

<property>

property标签中的lazy可选属性为true/false,这个特性需要类增强。

 

集合(set/list等)

集合标签中的lazy可选属性为true/false/extra,默认为true,代表只有在调用这个集合获取里面的元素对象时,才发出查询语句,加载其集合元素的数据。

首先插入数据到grade和student表中

public class HibernateTest {
    public static void main(String[] args) {
        Session session = HibernateFactory.currentSession();
        Transaction tx = session.beginTransaction();
        
        Grade grade = new Grade();
        grade.setName("grade1");
        
        Student student1 = new Student();
        student1.setName("student1");
        student1.setGrade(grade);
        
        Student student2 = new Student();
        student2.setName("student2");
        student2.setGrade(grade);
        
        grade.getStudents().add(student1);
        grade.getStudents().add(student2);
        session.save(grade);
    
        tx.commit();
        HibernateFactory.closeSession();
    }
}

 测试程序取出grade以及他对应的student列表(hbm文件中关闭了<class>级别的延迟加载)

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         Grade grade = (Grade)session.load(Grade.class, 1);
 7         
 8         tx.commit();
 9         HibernateFactory.closeSession();
10         System.out.println(grade.getStudents());
11     }
12 }

 以上代码在Session范围中load了一个Grade对象,因为并没有对<class>进行延迟加载,所以从数据库中取到了grade的数据,但在session范围外去获取grade的集合属性时程序缺抛出异常(

 failed to lazily initialize a collection of role),因为Hibernate对集合属性进行了延迟加载,如果想要代码正常运行,有以下三种修改方法

1:在集合标签<set>上设置lazy为false,取消集合的延迟加载,但这样每次都会把set中的数据全部取出来,占用了大量内存,影响程序性能

2:在集合标签<set>上设置fetch属性为"join",这个属性在后面fetch属性介绍时会再提到

3:使用hql语句加载grade数据,hql中显式的对两个对象进行连接,代码如下

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6     
 7         Query query = session.createQuery("from Grade as grade left outer join fetch grade.students where grade.id=:id");
 8         query.setParameter("id", 1);
 9         Grade grade = (Grade) query.uniqueResult();
10         
11         tx.commit();
12         HibernateFactory.closeSession();
13         System.out.println(grade.getStudents());
14     }
15 }

 

在集合的lazy属性中有一个值为extra,这是一种比较聪明的延迟加载策略,即调用集合的size/contains等方法的时候,hibernate并不会去加载整个集合的数据,而是发出一条"聪明"的SQL语句以便获得需要的值(例如通过sql中的count语句获取集合的size),只有在真正需要用到这些集合元素对象数据的时候,才去发出查询语句加载所有对象的数据。

 

<many-to-one>

<many-to-one>标签中的lazy可选属性为false/proxy/no-proxy,默认属性为proxy

public class HibernateTest {
    public static void main(String[] args) {
        Session session = HibernateFactory.currentSession();
        Transaction tx = session.beginTransaction();
        
        Student student = (Student)session.load(Student.class,1);
        
        tx.commit();
        HibernateFactory.closeSession();
        System.out.println(student.getName());
        System.out.println(student.getGrade().getName());
    }
}

以上代码会报异常,因为lazy的默认属性为proxy,作用与lazy="true"相似,启用延迟加载,而在Session范围外去取grade的值会出错,如果要使程序正确执行,有以下3种方法

1:lazy="false",关闭延迟加载

2:在session范围内访问grade对象或者对其初始化

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         Student student = (Student)session.load(Student.class,1);
 7         if(!Hibernate.isInitialized(student.getGrade())) {
 8             Hibernate.initialize(student.getGrade());
 9         }
10         tx.commit();
11         HibernateFactory.closeSession();
12         System.out.println(student.getName());
13         System.out.println(student.getGrade().getName());
14     }
15 }

3:配置Grade.hbm.xml,把它<class>标签中的lazy设置为false,这样不管<many-to-one>标签中lazy的值是什么,都会立刻加载grade对象

 

<many-to-one>的lazy标签还有一个取值为no-proxy,它和proxy效果一样,不过proxy对象不是动态,是在编译的过程中就创建的,需要进行特定的编译

 

<one-to-one>

<one-to-one>与<many-to-one>基本一致,标签中的lazy可选属性为false/proxy/no-proxy,默认属性为proxy,但是<one-to-one>中有一个属性为constrained,一旦设置为false(这也是它的默认值),不管lazy设置为何值,Hibernate都会采取预先抓取。

 

fetch抓取策略

<many-to-one>/<one-to-one>

<many-to-one>/<one-to-one>标签上fetch的可选取值有select/join,默认为select

fetch = "select"是在查询的时候先查询出一端的实体,然后在根据一端的查询出多端的实体,会产生1+n条sql语句;

fetch = "join"是在查询的时候使用外连接进行查询,不会差生1+n的现象。此时lazy会失效

 

集合标签 

集合标签(例如<set>/<list>)上fetch的可选取值有select/join/subselect,默认为select

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         Grade grade = (Grade)session.load(Grade.class,1);
 7         System.out.println(grade.getStudents());
 8         
 9         tx.commit();
10         HibernateFactory.closeSession();
11     }
12 }

当设置为select时,<set name="students" inverse="true" cascade="all" fetch="select">

输出的sql语句为:

 1 Hibernate: 
 2 select 
 3     grade0_.id as id1_0_0_, 
 4     grade0_.name as name2_0_0_ 
 5 from 
 6     grade grade0_ 
 7 where 
 8     grade0_.id=?
 9 
10 Hibernate: 
11 select 
12     students0_.gradeid as gradeid3_0_0_, 
13     students0_.id as id1_1_0_, 
14     students0_.id as id1_1_1_, 
15     students0_.name as name2_1_1_, 
16     students0_.gradeid as gradeid3_1_1_ 
17 from 
18     student students0_ 
19 where 
20     students0_.gradeid=?

先查询出一端的实体,当你真正访问关联关系的时候,才会执行第二条select语句抓取当前对象的关联实体或集合。

 

当设置为join时,<set name="students" inverse="true" cascade="all" fetch="join">

输出的sql语句为:

 1 Hibernate:
 2 select 
 3       grade0_.id as id1_0_0_, 
 4       grade0_.name as name2_0_0_, 
 5       students1_.gradeid as gradeid3_0_1_, 
 6       students1_.id as id1_1_1_, 
 7       students1_.id as id1_1_2_, 
 8       students1_.name as name2_1_2_, 
 9       students1_.gradeid as gradeid3_1_2_ 
10 from 
11      grade grade0_ 
12 left outer join 
13      student students1_ 
14 on 
15      grade0_.id=students1_.gradeid 
16 where 
17      grade0_.id=?

查询的时候使用外连接进行查询。

 

当设置为subselect时,<set name="students" inverse="true" cascade="all" fetch="subselect">

使用新的测试程序

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         List<Grade> grades = session.createQuery("from Grade").list();
 7         System.out.println(grades.get(0).getStudents().size());
 8         
 9         tx.commit();
10         HibernateFactory.closeSession();
11         System.out.println(grades.get(1).getStudents());
12     }
13 }

输出的sql语句为:

 1 Hibernate: 
 2 select 
 3     grade0_.id as id1_0_, 
 4     grade0_.name as name2_0_ 
 5 from 
 6     grade grade0_
 7 
 8 Hibernate: 
 9 select 
10     students0_.gradeid as gradeid3_0_1_, 
11     students0_.id as id1_1_1_, 
12     students0_.id as id1_1_0_, 
13     students0_.name as name2_1_0_, 
14     students0_.gradeid as gradeid3_1_0_ 
15 from 
16     student students0_ 
17 where 
18     students0_.gradeid 
19 in 
20     (select grade0_.id from grade grade0_)

另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合. 这个理解起来有点糊涂, 举个例子 : 如果你使用 Query 查询出了2个Grade 实体, 由于开启了懒加载,那么他们的 students 都没有被初始化, 此时手动初始化一个Grade 的 students,Hibernate 会将前面查询到的实体对象(2个Grade)的关联集合使用一条 Select 语句一次性抓取回来, 这样减少了与数据库的交互次数, 一次将每个对象的集合都给初始化了;(他是将上一次查询的 SQL 语句作为这一次查询的 SQL语句的 where 子查询, 所以上次查询到几个对象,那么这次就初始化几个对象的集合)。

分析上面的代码可以发现,在session范围外访问了grades.get(1).getStudents();因为使用的时"subselect"抓取策略,在访问grades.get(0).getStudents()时会把之前查询到的实体的关联集合全部一次性抓取回来,所以程序没有报错;但如果使用"select"抓取策略的话,由于session范围内没有访问grades.get(1).getStudents();所以在session范围外访问它会报错,这就是select和subselect的区别。

 

batch-size批量加载属性

batch-size可以看成是"select"和"subselect"的折中策略,既不想一次只加载一个实体的关联集合,也不想一次加载所有实体的关联结合,可以配合使用"select"和"batch-size",具体使用方法如下:

首先数据库中的数据如下

Grade.hbm.xml的关键配置如下

1 <set name="students" inverse="true" cascade="all" fetch="select" batch-size="2">
2             <key column="gradeid"/>
3             <one-to-many class="com.zlt.hibernatedemo.Student"/>
4 </set>

 

测试代码

 1 public class HibernateTest {
 2     public static void main(String[] args) {
 3         Session session = HibernateFactory.currentSession();
 4         Transaction tx = session.beginTransaction();
 5         
 6         List<Grade> grades = session.createQuery("from Grade").list();
 7         System.out.println(grades.get(0).getStudents().size());
 8         
 9         tx.commit();
10         HibernateFactory.closeSession();
11         System.out.println(grades.get(1).getStudents());
12     }
13 }

 

输出的sql语句

 1 Hibernate: 
 2 select 
 3     grade0_.id as id1_0_, 
 4     grade0_.name as name2_0_ 
 5 from 
 6     grade grade0_
 7 
 8 Hibernate: 
 9 select 
10     students0_.gradeid as gradeid3_0_1_, 
11     students0_.id as id1_1_1_, 
12     students0_.id as id1_1_0_, 
13     students0_.name as name2_1_0_, 
14     students0_.gradeid as gradeid3_1_0_ 
15 from 
16     student students0_ 
17 where 
18     students0_.gradeid in (?, ?)

 

结果分析:

以上代码在Session范围内抓取了grades.get(0).getStudents(),而在Session范围外访问了grades.get(1).getStudents(),由于使用的是"select"抓取策略而不是"subselect",所以应该报异常,但是程序却正确执行,而且确实抓取到了grades.get(1).getStudents(),这是因为设置了batch-size="2",Hibernate 使用一条 Select 语句一次性抓取2个grade实体对象的关联集合回来(例子中前一次查询共查询出4个grade实体对象,如果使用"subselect"抓取策略会一次性抓取这4个实体对象的关联集合),所以可以发现sql语句中的第二条用了in。

当抓取grades.get(0).getStudents()时,会一次抓取两个关联集合,即grades.get(0)和grades.get(1),此时在Session范围外访问grades.get(2)和grades.get(3)会报错

当抓取grades.get(1).getStudents()时,会一次抓取两个关联集合,即grades.get(1)和grades.get(2),此时在Session范围外访问grades.get(0)和grades.get(3)会报错

当抓取grades.get(2).getStudents()时,会一次抓取两个关联集合,即grades.get(2)和grades.get(3),此时在Session范围外访问grades.get(0)和grades.get(1)会报错

当抓取grades.get(3).getStudents()时,会一次抓取两个关联集合,即grades.get(3)和grades.get(0),此时在Session范围外访问grades.get(1)和grades.get(2)会报错

 

fetch与lazy组合情况

1、当lazy="true"  fetch="select" 的时候,这个时候是使用了延迟策略,开始只查询出一端实体,多端的不会查询,只有当用到的时候才会发出sql语句去查询。

2、当lazy="false"  fetch = "select" 的时候,个时候是使没有用延迟策略,同时查询出一端和多端,同时产生1+n条sql。

3、当fetch = "join"的时候,不管lazy设置为什么,这个时候延迟已经没有什么用了,因为采用的是外连接查询,同时把一端和多端都查询出来了。

posted on 2014-07-01 16:40  幸福小弥  阅读(3781)  评论(0编辑  收藏  举报