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设置为什么,这个时候延迟已经没有什么用了,因为采用的是外连接查询,同时把一端和多端都查询出来了。