zoukankan      html  css  js  c++  java
  • hibernate lazy和fetch属性介绍

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

  • 相关阅读:
    Selenium python 常用定位方法
    第四周总结
    python自然语言处理——提取关键词,标签
    python 调用百度地图api接口获取地理详细信息行政代码等
    python分词技术——jieba安装使用
    质量属性战术--6.易用性战术
    Kettle的使用——大数据清洗技术
    周总结2
    DataX的使用——大数据同步技术
    python编程:从入门到实践
  • 原文地址:https://www.cnblogs.com/zanglitao/p/3818098.html
Copyright © 2011-2022 走看看