好久没有认真写过博客了,今天就好好的写一篇吧!!!!!!!!!

当Hibernate 从数据库中加载某个对象(例如:Dept对象)时,如果同时自动加载所有的关联的某个对象(例如:Emp对象),而程序实际上仅仅需要访问Dept对象,那么这些关联的Emp对象就白白浪费了许多内存空间。发生这种情况的原因是:就是立即加载的问题。

1.什么是立即加载呢?

Hibernate 查询 Dept 对象时,立即加载并加载与之关联的Emp对象,这种查询策略称为 立即加载。

立即加载存在两大不足:

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

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

 

那么我们必须要解决这个问题,所以就要谈到了“延迟加载”的知识了,延迟加载策略能避免加载应用程序不需要访问的关联对象,以优化查询性能。

不过我们要知道有多种查询策略,接下来我们就一起来分析每一种查询策略的加载问题。

第一种:类级别的查询策略

类级别可选的加载策略包括立即加载和延迟加载,默认是延迟加载,如果,<class>元素的lazy的属性为true,表示采用延迟加载;如果lazy 属性 为 false,表示采用立即加载。

我们现在以代码来解释是最好的办法。

1.立即加载策略

我们在Dept.hbm.xml文件中 加 lazy=“false”  属性,即可。

表结构:

 

测试代码:

    Session session = HibernateUtil.currentSession();

        session.beginTransaction();

        Dept dept = (Dept)session.load(Dept.);

        System.out.println("部门名称"+dept.getdName());

        System.out.println("===================");

        Dept dept2 = (Dept)session.load(Dept.);

        System.out.println("部门名称"+dept2.getdName());

        session.getTransaction().commit();

        HibernateUtil.closeSessio();

测试结果:

 

我们知道使用Load方法加载的是代理对象,只会在属性里保存一个OID,但是如果在Dept映射文件中配置了类级别的lazy为false就代表加载该对象时立即加载,也就是立即检索一次数据库,发出了一条sql语句。

2.延迟加载

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

 

或是

 

如果程序加载一个持久化对象的目的是为了访问它的属性,这是我们可以采用立即加载,但是如果程序加载一个持久化对象的目的是为了获得它的引用,这是我们可以采用延迟加载,无须访问Dept对象的属性。

看例子:

   

   Dept dept = (Dept)session.load(Dept.);

        System.out.println("部门名称"+dept.getdName());

        Employee emp=new Employee();

        emp.setEname("李四");

        emp.setDept(dept);

        session.save(emp);

 

这段代码向数据库保存了 一个Employee 对象,它与已经存在的一个Dept持久化对象关联。如果在Dept 类级别 采用延迟加载,则 session.load()方法不会执行访问DEPT 表的select 语句,只返回一个Dept的代理对象,它的deptNo的属性值为1,其余属性都为NULL。session.save()方法执行的sql语句:

 

所以当,<class>元素的lazy属性为true时,会影响session.load()方法的各种运行时行为。举例说明:

1.如果加载的Dept对象在数据库中不存在时,不会抛出异常,只有运行dept.getxxx()时,才会抛出异常。

测试代码:

    Session session = HibernateUtil.currentSession();

        session.beginTransaction();

        Dept dept = (Dept)session.load(Dept.);

        System.out.println("部门名称"+dept.getdName());

        Employee emp=new Employee();

        emp.setEname("李四");

        emp.setDept(dept);

        session.save(emp);

 

当 deptNo为3不存在时,会抛出以下异常:

 

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

测试代码:

Session session = HibernateUtil.currentSession();

        session.beginTransaction();

        Dept dept = (Dept)session.load(Dept.);

        HibernateUtil.closeSessio();

        System.out.println("部门名称"+dept.getdName());

        session.getTransaction().commit();

        HibernateUtil.closeSessio();

 

从代码中我们可以看出,session被提前关闭,所以dept引用的Dept代理类的实例在Session范围内始终没有被初始化,所以当执行到  System.out.println("部门名称"+dept.getdName())时,会抛出以下异常:

 

 

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

3.import org.hibernate.Initialized()静态方法,用于在Session范围内显式初始化代理类实例,isInitialized()方法用于判断代理类实例是否已经被初始化。

代码:

Dept dept = (Dept)session.load(Dept.);

if(!Hibernate.isInitialized(dept)){

Hibernate.initialize(dept);

HibernateUtil.closeSessio();

System.out.println("部门名称"+dept.getdName());

}

 

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

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

代码:

Dept dept = (Dept)session.load(Dept.);

      System.out.println("编号:"+ dept.getDeptNo());

HibernateUtil.closeSessio();

System.out.println("部门名称"+dept.getdName());

 

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

 

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

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

代码:

 

Session session = HibernateUtil.currentSession();
        session.beginTransaction();
        Dept dept = (Dept)session.);
        System.out.println(dept);

 

结果:

 

由此可知,get方法永远不会执行Dept的代理类实例。

2.Query的list方法总是立即到数据库中查询Dept对象

 

代码:

List<Dept> query = session.createQuery("from Dept").list();
        for (Dept dept : query) {
            System.out.println(dept.getdName());
        }

结果:

 

到了这里,算是把第一种类级别的查询策略写的差不多了。

第二种:一对多和多对一关联的查询策略

添加一个小知识点:

01.一对多或者多对多检索策略由lazy和fetch共同确定

 

02.fetch取值

    Join:迫切    Lazy:决定关联对象初始化时机

左外连接

    Select:多条简单SQL(默认值)

    Subselect:子查询

03.fetch和lazy组合

  解析:fetch=”join” lazy会被忽略,迫切左外连接的立即检索

        Fetch=”s    Fetch:决定SQL语句构建形式

elect” lazy=”false”  多条简单SQL立即检索

        Fetch=”select” lazy=”true”  多条语句延迟检索

        Fetch=”select” lazy=”extra”  多条语句及其懒惰检索

        Fetch=”subselect” lazy=”false”  子查询立即检索

        Fetch=”subselect” lazy=”true”  子查询延迟检索

        Fetch=”subselect” lazy=”extra”  子查询及其懒惰检索

Extra:及其懒惰,只有访问集合对象的属性时才会加载,访问集合本身的属性时(例如,集合大小,生成count),不会立即加载。

注意:query的list()会忽略映射文件配置的左外连接查询,fetch,此时lazy属性重新生效。

 

 

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

  <!-- 双向     cascade:级联     inverse:反转 -->
           <!--set表明Dept类的emps属性为set集合类型  -->
           <!--order-by 对集合排序  order-by="dName asc   order-by="dName desc-->
           <set name="emps" inverse="true" lazy="true">
           <!--employee表的外键 deptNo  -->
           <key column="deptNo"></key>
           <!--一对多    class 属性设定与所关联的持久化类 为employee -->
           <one-to-many class="Employee"/>
           </set>

 

这里的<set>元素有lazy属性,主要取决于emps集合被初始化的时机,到底是在加载Dept对象时就被初始化,还是在程序访问emps集合时被初始化。

1.立即加载

Dept.hbm.xml的配置文件:

测试代码:


        Dept dept = (Dept)session.);
        System.out.println(dept.getdName());
        

结果:执行Session的get方法时,对于Dept对象采用类级别的立即加载策略,对于Dept对象的emps集合(Dept关联所有的employee对象),采用一对多关联的立即加载策略。

 

 从这个结果我们可以看到,Hibernate加载了一个Dept对象和Employee对象,但是我们知道很多情况下,不需要访问Employee对象,所以我们就得用了 延迟加载策略。

2.延迟加载

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

 

测试代码:

    Dept dept = (Dept)session.);
        System.out.println(dept.getdName());
        

结果:

很明显,只执行了一条sql语句,即仅仅加载了Dept对象。

Session的get方法,返回的是Dept对象的emps属性引用一个没有被初始化的集合代理类实例。换句话说,此时的emps集合中没有存放任何Emp对象,只有emps集合代理类实例被初始化时,才回到数据库查询所有与Dept关联的Emp对象。

测试代码:

    Dept dept = (Dept)session.);
        System.out.println(dept.getdName());

        for (Employee emp : dept.getEmps()) {
            System.out.println(emp.getEname());
        }

结果:

 

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

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

代码:

    Dept dept = (Dept)session.);
        Set<Employee> emp=dept.getEmps();
        System.out.println(dept.getdName());

        Iterator<Employee> itee=emp.iterator();//emps被初始化

02.通过hibernate的静态方法initialize()来初始化它。

    Dept dept = (Dept)session.);
        Set<Employee> emp=dept.getEmps();
        System.out.println(dept.getdName());
        Hibernate.initialize(emp);//emps被初始化

3.增强延迟加载

lazy="extra"

配置如下:

 

增强延迟加载策略能进一步延迟Dept对象的emps集合代理类实例初始化时机。当应用程序第一次访问emps属性的iterator()时,会导致emps集合代理类的实例初始化。但是当当应用程序第一次size(),isEmpty(),或是 contains()方法时,emps不会初始化emps集合代理实例。仅仅通过查询select语句必要信息。

测试代码:


Dept dept = (Dept)session.get(Dept.class, 1);
//不会初始化emps集合代理类实例
int size = dept.getEmps().size();
System.out.println(size);

//会初始化emps集合代理类实例
Iterator<Employee> iterator = dept.getEmps().iterator();
System.out.println(iterator);


结果:

现在是第三种:多对一关联的查询策略

lazy=proxy

 Employee.hbm.xml中但我配置:

1.延迟加载策略。

测试代码:

Employee em=(Employee) session.);//仅仅执行em对象的sql语句
        Dept dept = em.getDept();
        System.out.println(dept.getdName());//执行Dept对象

当Sesson执行get()方法时,仅仅立即执行查询Employee对象的select语句。当Employee 对象引用Dept代理类实例,这个代理类实例的IOD由Employee 表的DeptNo外键值决定。 当执行dept.getdName()时,hibernate 初始化Dept代理类实例,执行以下select语句到数据库中加载Dept对象。

结果:

 

无代理延迟加载:

lazy="no-proxy" 

测试代码:

        Employee em=(Employee) session.);//仅仅执行em对象的sql语句
        Dept dept = em.getDept();
        System.out.println(dept.getdName());//执行Dept对象

如果Employee对象的dept属性使用无代理延迟加载,即<many-to-many>元素的lazy属性为no-proxy,当执行 get方法时,加载的Employee的dept属性为NULL,当执行到  em.getDept()时,将触发hibernate执行查询Dept 表的select 语句,从而加载Dept对象。

结果:

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

立即加载:

lazy=“false”

测试:

    Employee em=(Employee) session.);

结果:

可以看到,执行了两条sql语句。 

Open Session In View 模式

Open Session In View 模式的作用:

 Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 Hibernate Session 已经关闭,这些导致延迟加载数据的访问异常。

在Java Web 应用中,通常需要调用ibernate API 获取到显示的某个要显示的某个对象并传给相应但的视图JSP, 并在JSP中从这个对象导航到与之关联的对象或集合数据。这些关联对象或集合数据如果是被延迟加载的,hibernate 就会抛出以下异常:

这是因为在调用完hibernate完之后,Session 对象已经关闭了。针对这个问题,hibernate 社区提供了Open Session In View 模式 的解决方案!!

代码示例:

HibernateUtil代码

 private static final ThreadLocal<Session> sessionTL=new ThreadLocal<Session>();
//私有的静态的配置对象
 private static Configuration configuration;
//私有的静态的工厂对象
 private final static SessionFactory sessionFactory;
 //静态代码块,负责给成员变量赋值
 static{
     configuration=new Configuration().configure();
     sessionFactory=configuration.buildSessionFactory();
 }
 //从SessionFactory  连接池            获取一个和当前thread bind session
 public static Session currentSession(){
    //2.返回当前的线程其对应的线程内部变量

      //sessionTL的get()方法根据当前线程返回其对应的线程内部变量,

     //也就是我们需要的Session,多线程情况下共享数据库连接是不安全的。

       //ThreadLocal保证了每个线程都有自己的Session.

     Session session=sessionTL.get();
     //如果当前线程是session 为空=null ,则打开一个新的Session
     if(session==null){
         //创建一个session对象
         session=sessionFactory.openSession();
         //保存该Sessioon对象到ThreadLocal中
         sessionTL.set(session);
     }
     return session;
 }
 //关闭Session
 public  static void closeSessio(){
     Session session=sessionTL.get();
     sessionTL.set(null);
     session.close();

 }

biz层代码:

HibernateDao dao=new HibernateDao();
    public Object get(Class clazz,Serializable id){
           Object obj= dao.get(clazz, id);return obj;
    }

dao层代码:

public Object get(Class clazz,Serializable id){
           Object result= HibernateUtils.currentSession().load(clazz, id);
           return result;
    }

filter层代码:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        Session session = null;
        Transaction tx = null;
        try {

            session = HibernateUtils.currentSession();
            System.out.println("filter\t"+session.hashCode());
            tx = session.beginTransaction();
            // 执行请求处理链    双向过滤
            chain.doFilter(request, response);
            // 返回响应时,提交事务
            tx.commit();
        } catch (HibernateException e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            // 关闭session
            HibernateUtils.closeSession();
        }
    }

过滤器在网站xml中的配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name></display-name>
 <!-- 过滤器 -->
 <filter>
   <filter-name>openSessionInView</filter-name>
   <filter-class>cn.happy.filter.OpenSessionInViewFilter</filter-class>
 </filter>
 <filter-mapping>
    <filter-name>openSessionInView</filter-name>
    <url-pattern>/*</url-pattern>
 </filter-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

index.jsp页面的代码:

<body>
    <%
          HibernateBiz biz=new HibernateBiz();
        Emp emp=(Emp)biz.);
      %>
     <%=emp.getEmpName() %>
  </body>

这个代码只是一个例子而已,你可以写别样的代码。这样就算完成了Open Session In View 模式。

再总结一遍:

关于No Session的这个问题,有了六种的解决方案:

方案一
在biz层 把load 改成get
方案二
/* if (!Hibernate.isInitialized(obj)) {
Hibernate.initialize(obj);
}*/
方案 三 :在 映射文件中 ,类级别 <set> 中加上 lazy =“false”
方案四: 在biz 层 先用一道 需要的UI使用 到的属性 ,然后在biz关闭
方案五:把实体类 改成 用 final 修饰,我们知道,延迟加载的原因是 内存中 有代理对象 (其实是emp 类的子类),所以当我们设为 该类 不能 有子类
方案六:Open Session In View 模式。

在上面都已经用代码做例子了,够清楚了的。

Hibernnate延迟加载策略(这么详细你还看不懂)的更多相关文章

  1. 还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下

    Java 8 新特性系列文章索引. Jdk14都要出了,还不能使用 Optional优雅的处理空指针? Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下? 还看不懂同事的代码?Lambda ...

  2. 还看不懂同事的代码?Lambda 表达式、函数接口了解一下

    当前时间:2019年 11月 11日,距离 JDK 14 发布时间(2020年3月17日)还有多少天? // 距离JDK 14 发布还有多少天? LocalDate jdk14 = LocalDate ...

  3. Node.js(转) -- 临时来说还看不懂!

    转自:http://blog.jobbole.com/53736/ 本文由 伯乐在线 - Lellansin 翻译.未经许可,禁止转载!英文出处:toptal.欢迎加入翻译组. 介绍 JavaScri ...

  4. 还看不懂同事代码?快来补一波 Java 7 语法特性

    前言 Java 平台自出现到目前为止,已经 20 多个年头了,这 20 多年间 Java 也一直作为最流行的程序设计语言之一,不断面临着其他新兴编程语言的挑战与冲击.Java 语言是一种静态强类型语言 ...

  5. 花了三天整理,Spring Cloud微服务如何设计异常处理机制?还看不懂算我输

    前言 首先说一下为什么发这篇文章,是这样的.之前和粉丝聊天的时候有聊到在采用Spring Cloud进行微服务架构设计时,微服务之间调用时异常处理机制应该如何设计的问题.我们知道在进行微服务架构设计时 ...

  6. Mybatis缓存及延迟加载策略

    一:引言 通过前面几篇的文章介绍了Mybatis的一对一.一对多.多对多关系的配置及实现,可是大家发现了吗?在执行关联查询的时候,直接会把当前查询的主表里包含的副表也查询后封装到对象里,其实在实际开发 ...

  7. MySQL 笔记整理(3) --事务隔离,为什么你改了我还看不见?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 3) --事务隔离,为什么你改了我还看不见? 简单来说,事务就是要保证一组数据操作,要么全部成功,要么全部失败.在MySQL中,事务 ...

  8. Hibernate延迟加载策略

    所谓懒加载(lazy)就是延时加载,就是当在真正需要数据的时候,才真正执行数据加载操作 至于为什么要用懒加载呢,就是当我们要访问的数据量过大时,明显用缓存不太合适,因为内存容量有限 ,为了减少并发量, ...

  9. Mybatis 延迟加载策略

    延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据.延迟加载也称懒加载. 好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速 ...

随机推荐

  1. MySQL+Amoeba实现数据库主从复制和读写分离

    MySQL读写分离是在主从复制的基础上进一步通过在master上执行写操作,在slave上执行读操作来实现的.通过主从复制,master上的数据改动能够同步到slave上,从而保持了数据的一致性.实现 ...

  2. 《Ansible权威指南》笔记(2)——Inventory配置

    四.Inventory配置ansible通过Inventory来定义主机和组,使用时通过-i指定读取,默认/etc/ansible/hosts.可以存在多个Inventory,支持动态生成.1.定义主 ...

  3. linux基础命令

    系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...

  4. windows10简单试用(多图,连薛定谔的猫都杀死了)

    为了大家看起来方便,我的截图都是gif的,比较小,但是颜色会有色差,相信大家不介意的 昨天windows10可以下载第一时间就下了玩玩 由于是技术预览,所以不打算替换之前的系统,只装在虚拟机里玩玩就好 ...

  5. Python学习--02输入和输出

    命令行输入 x = input("Please input x:") y = raw_input("Please input x:") 使用input和raw_ ...

  6. 【java】细说 JAVA中 标注 注解(annotation)

    Java注解是附加在代码中的一些元信息,用于一些工具在编译.运行时进行解析和使用,起到说明.配置的功能.注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用 下面我们来详细说说这个注解,到底是怎么一 ...

  7. Lisp和SICP

         大概不少programmer都看过<黑客与画家>,作者用了整整一章的篇幅讨论Lisp的强大.我自然就会手痒痒.      几个月前,几天内攻城略地搞定了Python,用的方法便是 ...

  8. 图的生成树(森林)(克鲁斯卡尔Kruskal算法和普里姆Prim算法)、以及并查集的使用

    图的连通性问题:无向图的连通分量和生成树,所有顶点均由边连接在一起,但不存在回路的图. 设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 ...

  9. IntelliJ IDEA 内存优化最佳实践

    本文作者在和同事的一次讨论中发现,对 IntelliJ IDEA 内存采用不同的设置方案,会对 IDE 的速度和响应能力产生不同的影响. Don't be a Scrooge and give you ...

  10. iOS Interface Builder:在.xib文件中加载另一个.xib文件

    在开发中,经常会用到一个需要重复使用的模块,比如好友列表中每个用户的展示或每条动态,这些都是相同的模版,这样我们就可以把这个部分提取出来放到一个单独的.xib中.那么提取出的.xib如何在其他.xib ...