• Hibernate 中 load() 方法导致的 noSession 异常


    之所以要写这个,是因为最近碰到了一个延迟加载的 load() 导致出现 noSession 的异常。

    下面第三种方式解决这个问题需要用到一个本地线程的对象,也就是 ThreadLocal 类,之前写过关于这个对象,可以看这个博客本地线程 ThreadLocal 类

    提一嘴 get 和 load 的区别:详细可以查看这篇博客 load() 和 get() 的区别

    1.get() 采用立即加载方式,而 load() 采用延迟加载;
    2.get() 方法执行的时候,会立即向数据库发出查询语句,而 load() 方法返回的是一个代理(此代理中只有一个 OID 属性),只有等真正使用该对象属性的时候,才会发出 sql 语句并执行
    3.如果数据库中没有对应的记录 , get() 方法返回的是 null . 而 load() 方法出现异常 ObjectNotFoundException

          我在数据层中封装了一个 load() 方法,根据用户 Id 获取用户对象:

    public UserModel getUserById(Long uuid) {
            return this.getHibernateTemplate().load(UserModel.class, uuid);
            //return this.getHibernateTemplate().get(UserModel.class, uuid);
        }

            当在业务层调用了这个方法后,获得一个 userModel 对象,当展示用户信息时,就会报出 noSession 问题,

    public String get(){
            //报出了 org.hibernate.LazyInitializationException: could not initialize proxy - no Session异常
            UserModel temp = userEbi.get(1L);
            System.out.println(temp.getName());
            System.out.println(temp.getAge());
            return "haha";
        }

    报出的异常如下:

    image

    原因:

            这是因为数据层提供的 load() 方法具有延迟加载的特性,在调用 load方法时,Hibernate 不会立即执行 sql 语句,而是动态的生成一个 UserModel 的代理对象实例,这个代理对象只具有 OID,也就是 uuid 值【这个值来自于形参,而不是数据库】,该对象的其他信息都没有。

    这个动态代理对象所具有的特性有:

    1. 代理类对象和真实的 UserModel 对象无异,但是只具有 OID,其他属性均为 null,所以会比较省内存,这也是延迟加载的好处
    2. 当第一次访问这个代理类对象时,调用器 getXxx 方法时不会出问题,Hibernate 才会初始化这个代理对象,并会自动去执行 sql 语句去查询其数据库中的数据,【也就是 Service 层中的同一个方法,一旦这个方法弹栈,即事务提交结束,session 被关闭,】。

       

           当需要这个只具有 OID 的代理对象传递到了业务层(如果在事务关闭session之前立马进行二次查询,就不会出问题),此时对象仍是一个延迟加载对象(只具有 OID),如果传递到了表现层(action内),此时该对象依然是延迟加载对象(但此时早在业务层,方法早已弹栈,事务就已经将 session 关闭了,一旦要进行二次查询就会报异常, 因为 session对象已经被刷新了,也就是连接断开了,session 中存储的数据早已丢失),值得一说的是在表现层使用二次查询是比较正常的需求。

    解决这个问题有三个途径:

    • 在全局取消延迟加载数据
    • 在局部取消延迟加载数据
    • 将 session 的范围扩大,扩大到表现层【此时就是 action 内 】

    第一种:在全局取消延迟加载数据,【这里的全局指的是某个表,并不是整个数据库】

    <?xml version="1.0"?>  
        <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
                                           "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
        <hibernate-mapping package="com.msym.entity">  
        <!-- 取消对于 user_db 表的延迟加载属性 -->  
         <class name="User" table="user_db" lazy="false" >  
          <id name="id">  
           <generator class="identity"/>  
          </id>  
          <property  name="password" />  
          <property  name="name" />
          <property  name="nickName" />    
          <property  name="age" />  
          <property  name="birthday" />  
         </class>  
        </hibernate-mapping>

    优点:简单方便;

    缺点:对于这个表,取消了延迟加载,在很多情况下会导致内存占用大的问题,取出了原本不需要的数据。

    第二种:在局部取消延迟加载,【在 Service 层和 dao 层都可以局部取消延迟加载属性】

    public UserModel getUserById(Long uuid) {
            UserModel u =  this.getHibernateTemplate().load(UserModel.class, uuid);
            Hibernate.initialize(u);
            return u;
    
            //return this.getHibernateTemplate().get(UserModel.class, uuid);
        }

    优点:相比第一种,更加具有针对性,更加的灵活;

    缺点:代码量稍稍多了一点点【但是也不要紧】。

    第三种:扩大 session 的范围,

           也就是要做到当前 session 在 Service 层的方法弹栈后不随事务的提交而关闭,直到一次请求与响应完成才关闭。这时用到的一个技术就是 openSessionInView,将 session 与当前请求对应的线程绑定在一起【此 session 并非浏览器会话级别的 session,而是数据库连接的 session】,这需要在 web.xml 中配置一个过滤器,这时注解的事务的任务就是开启事务,刷新事务;关闭事物的功能交给了 OpenSessionInViewFilter 这个类了,因为这个过滤器配置在一个,请求对象 request 最先接触到它,请求结束时的最后一个过滤器也是它(这就是过滤器链了,往返要都过滤一次)。代码在下面:(一定要将 OpenSessionInViewFilter 配置在所有过滤器的前面)

    <?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">
    
        <!-- applicationContext对象加载仅加载一次,服务器启动时加载,使用web中的监听器机制 -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        
        <!-- OpenSessionInView解决noSession问题,一定要配置在核心过滤器的前面 -->
        <filter>
            <filter-name>openSessionInView</filter-name>
            <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    
            <init-param>
                <param-name>sessionFactoryBeanName</param-name>
                <param-value>sessionFactory</param-value>
            </init-param>
    
            <init-param>
                <param-name>singleSession</param-name>
                <param-value>true</param-value>           
            </init-param>
    
            <init-param>
                <param-name>flushMode</param-name>
                <param-value>AUTO</param-value>        
            </init-param>
    
        </filter>
        <filter-mapping>
            <filter-name>openSessionInView</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    
        <!-- struts核心过滤器 -->
        <filter>
            <filter-name>struts2</filter-name>
            <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
        </filter>
    
        <filter-mapping>
            <filter-name>struts2</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>

    OpenSessionInViewFilter 配置了三个参数:singleSession,sessionFactoryBeanName ,flushMode,

    这几个属性都有其默认值,分别是,true,SessionFactory,MANUAL【手动提交事务】。

    其中只是将 flushMode的默认属性修改了,是为了防止出现 FlushModel 相关的异常。

          因为上面使用的是【org.springframework.orm.hibernate3.support.OpenSessionInViewFilter】这些属性在其中都能找到,

    image

    但是如果使用的是 hibernate4.support 的话,这些属性的位置就发生了变化,
    其中:

           (1) . sessionFactoryBeanName 还是在 OpenSessionInViewFilter里面,默认值也是 sessionFactory 如下图:

    image
           (2) . singleSession 默认值也是 true

           (3) . flushMode 属性来自于 Session 这个接口,默认值也是MANUAL【手动提交事务】。

    image

           它表示事务的提交方式,有如下几种:

    image

    优点:配置简单,

    缺点:拦截了所有请求,效率不高,如果对象导航层级较多,会导致页面的加载速度变慢,延长了 Session的生命周期,加重了服务器的负担。

    总结:

            到最后推荐使用第二种,也就是局部取消延迟加载特性,灵活性更好。

  • 相关阅读:
    CF1033F Boolean Computer
    CF1027G X-mouse in the Campus
    LOJ2570 [ZJOI2017]线段树
    清华强基&交大浙大三一
    java制作unicode代码在excel中批量导入图片
    laravel8新功能和笔记
    缩小图片比例大小和占用空间
    2018-2019-2 20175216张雪原 实验五《网络编程与安全》实验报告
    2018-2019-2 20175216张雪原 实验四《Android程序设计》实验报告
    20175216 《Java程序设计》第十一周学习总结
  • 原文地址:https://www.cnblogs.com/daimajun/p/7074613.html
Copyright © 2020-2023  润新知