懒加载异常处理

今天聊到Hibernate的懒加载异常和1+N问题。这两个应该是java面试必问的问题,好了,废话不说了,我们来看看这两个问题。

什么是懒加载?
答:在Hibernate框架中,当我们要访问的数据量过大时,明显用缓存不太合适, 因为内存容量有限 ,为了减少并发量,减少系统资源的消耗,这时Hibernate用懒加载机制来弥补这种缺陷,但是这只是弥补而不是用了懒加载总体性能就提高了。
我们所说的懒加载也被称为延迟加载,它在查询的时候不会立刻访问数据库,而是返回代理对象,当真正去使用对象的时候才会访问数据库。

懒加载的作用:性能优化:
分类:
1、类得懒加载
2、集合的懒加载
3、单端关联的懒加载(多对一)

![这里写图片描述](http://img.blog.csdn.net/20151213155704143)        

懒加载
1、类的懒加载
1、利用session.load方法可以产生代理对象
2、在session.load方法执行的时候并不发出sql语句
3、在得到其一般属性的时候发出sql语句
4、只针对一般属性有效,针对标示符属性是无效的
5、默认情况就是懒加载
2、集合的懒加载
false 当session.get时,集合就被加载出来了
true 在遍历集合的时候才加载
extra
针对集合做count,min,max,sum等操作
3、单端关联的懒加载(多对一)

<class name="com.test.Student"table="student" lazy="true">  
这种方式等价于  
<class name="com.test.Student"table="student" proxy="com.test.Student">  

懒加载异常:

Hibernate框架的作用就是让代码和数据库都以面向对象的存在方式联系起来,从数据库取出数据并通过Hibernate转化成内存中的对象A,我们都知道对象的成员也有可能是另一个类的实例(另一个类的对象B),而这个成员对象B在数据库中也有自己对应的数据。很多时候我们取出的对象A其实并不需要B对象做业务,如果全部取出无疑是造成性能的浪费,因此在开发中我们常常把A对象的装载,也就是从数据库里取数据并转化成A对象的过程定义为懒加载,顾名思义,懒加载的意思就是取数据的时候不取成员对象B所对应的数据。在需要用到B的时候再从数据库中取对象。另外我们要知道一点从数据库里取数据是通过session去取的,如果当你的程序准备加载B对象的时候,发现session关闭了或者过期了便会产生懒加载异常。

懒加载异常解决方案:

在网上找的解决方案,仅供参考,思路是正确的

1.直接不适用懒加载:lazy=”false”,这是相当一般的做法(但是在实际的开发中一般不可取),如果确实需要懒加载,这显然是不能解决问题滴。

2.OpenSessionInViewFilter或者OpenSessionInViewInterceptor,正如名字所示,这是解决在view层里调用dao层的出现的懒加载问题的方法。其目的是给web request提供单一的hibernate session,但是它也只能给web request提供hibernate session。它会过滤所有指定的web request,并提供持续的session连接。

3.HibernateInterceptor 如果有某一个hibernate请求不是经由web request发起的,而是由quartz这样的定时任务发起的,那么quartz怎么样才能得到hibernate session呢?解决办法就是使用HibernateInterceptor。也就是说不是通过控制(过滤)web请求来实现。

4.另外还有一种就是你需要用懒加载从数据库加载某对象在发HQL的时候加上特定的配置

struts2+spring+hibernate 懒加载异常:org.hibernate.LazyInitializationException: failed to lazily initialize

Hibernate的Lazy初始化1:n关系时,必须保证是在同一个Session内部使用这个关系集合,不然Hiernate将抛出异常:org.hibernate.LazyInitializationException: failed to lazily initialize of….

这种异常有时是因为在用Hibernate的load方法取数据库的数据时发生的,因此往往load出来的对象是一个Proxy即一个代理对象,而取出来的方法执行完毕后,session就会关闭,而当返回这个代理对象到客户端解析jsp页面时才会真正的发SQL语句,而此时的session已经关闭,因此会抛出此异常。

解决方法如下:
(1)使用get方法来替代load方法,但是要注意的时若get方法的数据表若还关联了其他的对象则依然不会在get对象时发出SQL语句,依然会出现这个异常。
(2)扩大session的生命周期。即可采用Spring提供的一个filter:OpenSessionInView。
完整的filter配置如下:

<filter>
   <filter-name>OpenSessionInView</filter-name>
   <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>OpenSessionInView</filter-name>
    <url-pattern>/*</url-pattern>       
</filter-mapping>

注意:这个filter必须写在web.xml文件的filter名为struts2的前面。

could not initialize proxy - no Session
异常:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:57)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:111)

原因:hibernate3 many-to-one的默认选项是 lazy = “proxy”
解决方法: & 中设置 lazy=”false”

Hibernate的Lazy初始化1:n关系时,必须保证是在同一个Session内部使用这个关系集合,不然Hiernate将抛出异常:org.hibernate.LazyInitializationException: failed to lazily initialize of….

两种处理方法

二、用OpenSessionInViewFilter过滤器,注意hibernateFilter过滤器和struts2过滤器在映射时的先后顺序。同时要配置事物处理,否则会导致session处于只读状态而不能做修改、删除的动作。
即在web.xml文件中如下配置:

     <!-- Spring ApplicationContext配置文件的加载目录。 -->  
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath*:spring/applicationContext.xml</param-value>
 </context-param>
 <!-- 解决延迟加载的问题 -->
 <filter>
  <filter-name>hibernateFilter</filter-name>
  <filter-class>
   org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
  </filter-class>
 </filter>

 <!-- 继承Struts2的FilterDispatcher类,具备GBK等编码设定功能与struts2的action过滤功能。 -->
 <filter>
  <filter-name>struts2</filter-name>
  <filter-class>
   com.iman.nrms.opm.web.common.FilterDispatcher
  </filter-class>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>GBK</param-value>
  </init-param>
 </filter>
 <filter>
         <filter-name>struts-cleanup</filter-name>
         <filter-class>
             org.apache.struts2.dispatcher.ActionContextCleanUp
         </filter-class>
     </filter>

 <filter-mapping>
  <filter-name>hibernateFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

 <filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

 <filter-mapping>
  <filter-name>struts-cleanup</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 Hibernate Session 已经关闭,这些导致延迟加载数据的访问异常。而Spring为我们提供的OpenSessionInViewFilter过滤器为我们很好的解决了这个问题。OpenSessionInViewFilter的主要功能是使每个请求过程绑定一个 Hibernate Session,即使最初的事务已经完成了,也可以在 Web 层进行延迟加载的操作。OpenSessionInViewFilter 过滤器将 Hibernate Session 绑定到请求线程中,它将自动被 Spring 的事务管理器探测到。所以 OpenSessionInViewFilter 适用于 Service 层使用HibernateTransactionManager 或 JtaTransactionManager 进行事务管理的环境,也可以用于非事务只读的数据操作中。
request–>open session–>打开连接、开始事务–>持久操作–>渲染(关闭连接、session)–>response 其中一些过程省略了,不是很关心。

最近在做一个项目,采用的是SSH2框架,要实现一个级联功能,具体实现如下:

Department部门包括许多下级Department,在查询第一级Department时,一切OK,但是当查询第一级Department下的子Department时,出现了如下错误,其中Department.hbm.xml中的配置文件为

在CODE上查看代码片派生到我的代码片

<many-to-one name="parent" class="Department"column="parentId"></many-to-one>  
    这里表示Department实体中有parent这个属性,一个parent有多个Department,也就是多对一关系

以及Stacktraces

1、org.apache.jasper.JasperException:javax.el.ELException: Error reading ‘name’ on typecn.itcast.oa.domain.Department_$$_javassist_1

。。。。。省略。。。。。。

2、javax.el.ELException: Error reading ‘name’ ontype cn.itcast.oa.domain.Department_$$_

javassist_1

。。。。。省略。。。。。。

3、org.hibernate.LazyInitializationException:could not initialize proxy - no Session

。。。。。省略。。。。。。

在上面的三条错误消息中,第三条为关键错误提示LazyInitializationException(懒加载异常在默认情况下,hibernate为懒加载),这意味着在读取数据的时候,Session已经关闭。

解决办法:

1、 设置懒加载为false,在默认情况下,hibernate为懒加载,因此需要设置不为懒加载,在Department.hbm.xml中设置如下:

<many-to-one name="parent"class="Department" column="parentId"lazy="false"></many-to-one>  
     把lazy的值设置为false,也就是说,当加载了父Department后,他的所有子Department都会被加载,这就会出现另外一个问题:当父Department下有很多子Department时,会加载所有的子Department,会造成性能很低。

那么我们能不能把他改为用的时候才加载,不用的时候则不加载?(默认还是懒加载,但是要你在用的时候能找到Session,能找到Session就能从数据库中读取数据)

2、采用拦截器

上面代表一次请求,需要经过Action和拦截器,左边方框为Action,中间方框为Result(页面),右边方框为Filter拦截器。

     Action表示我们要执行的结果,在Action里调用的是Service业务方法(Service方框),我们常用的做法是在Service中开和关事物,以及openSession和close Session,由于我们是在Result(页面)中才使用到懒加载的属性(此时Session已经关闭)。为了解决这个问题,必须要把close Session这一步推迟到Result后才能关闭。这里我们采用的是spring中OpenSessionInViewFilter(这是一个过滤器)来实现。

具体代码如下:

Department.hbm.xml中的配置保持不变,如下所示:

<many-to-one name="parent" class="Department"column="parentId"></many-to-one>  

在web.xml中添加一个过滤器,如下所示:

<!-- 配置Spring的OpenSessionInViewFilter过滤器,以解决Hibernate的懒加载异常(LazyInitializationException) -->  
    <filter>  
       <filter-name>OpenSessionInViewFilter</filter-name>  
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>  
    </filter>  
    <filter-mapping>  
       <filter-name>OpenSessionInViewFilter</filter-name>  
       <url-pattern>*.action</url-pattern>  
    </filter-mapping

这样就可以实现需要的时候才加载,不需要的时候不加载,同时默认页为懒加载

一般情况下,如果没有使用Filter或者将lazy设置为false的话,在Action方法中使用完Session就会关闭Session,因此如果在Action方法外使用Session中的数据就会报懒加载异常。

     OpenSessionInViewFilter的作用是将Session的关闭放到Filter中,因此,我们在Action方法外使用Session时,就不会出现Session已经关闭的异常了。

下面这张图跟上面这张图差不多,可以参考一下。

你可能感兴趣的:(Hibernate,异常处理,struts,什么是懒加载,怎么处理懒加载异常)