Hibernate延迟加载和OpenSessionInView

          ThreadLocal<T>类在Spring,Hibernate等框架中起到了很大的作用.ThreadLocal类的使用虽然是用来解决多线程的问题的,但是还是有很明显的针对性.synchronized是用来处理多线程环境下的数据同步,而ThreadLocal只是为了保存当前线程私有的某种状态.

1.最明显的,ThreadLoacl变量的活动范围为某线程,并且我的理解是该线程“专有的,独自霸占”,对该变量的所有操作均有该线程完成!也就是说,ThreadLocal不是用来解决共享,竞争问题的。典型的应用莫过于Spring,Hibernate等框架中对于多线程的处理了

private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

 

          这段代码,每个线程有自己的ThreadLocalMap,每个ThreadLocalMap中根据需要初始加载threadSession,这样的好处就是介于singleton与prototype之间,应用singleton无法解决线程,应用prototype开销又太大,有了ThreadLocal之后就好了,对于需要线程“霸占”的变量用ThreadLocal,而该类实例的方法均可以共享。

 

2.关于内存泄漏:

             虽然ThreadLocalMap已经使用了weakReference,但是还是建议能够显示的使用remove方法。

 

 Hibernate与延迟加载:

          Hibernate对象关系映射提供延迟的与非延迟的对象初始化。非延迟加载在读取一个对象的时候会将与这个对象所有相关的其他对象一起读取出来。这有时会导致成百的(如果不是成千的话) select 语句在读取对象的时候执行。这个问题有时出现在使用双向关系的时候,经常会导致整个数据库都在初始化的阶段被读出来了。当然,你可以不厌其烦地检查每一个对象与其他对象的关系,并把那些最昂贵的删除,但是到最后,我们可能会因此失去了本想在 ORM 工具中获得的便利。
         一个明显的解决方法是使用 Hibernate 提供的延迟加载机制。这种初始化策略只在一个对象调用它的一对多或多对多关系时才将关系对象读取出来。这个过程对开发者来说是透明的,而且只进行了很少的数据库操作请求,因此会得到比较明显的性能提升。这项技术的一个缺陷是延迟加载技术要求一个 Hibernate 会话要在对象使用的时候一直开着。这会成为通过使用 DAO 模式将持久层抽象出来时的一个主要问题。为了将持久化机制完全地抽象出来,所有的数据库逻辑,包括打开或关闭会话,都不能在应用层出现。最常见的是,一些实现了简单接口的 DAO 实现类将数据库逻辑完全封装起来了。一种快速但是笨拙的解决方法是放弃 DAO 模式,将数据库连接逻辑加到应用层中来。这可能对一些小的应用程序有效,但是在大的系统中,这是一个严重的设计缺陷,妨碍了系统的可扩展性。

 

Hibernate有很多值得学习的地方,这里我们主要介绍Hibernate ThreadLocal,包括介绍Hibernate官方开发手册标准示例等方面。

       Hibernate ThreadLocal它会为每个线程维护一个私有的变量空间。实际上, 其实现原理是在JVM 中维护一个Map,这个Map的key 就是当前的线程对象,而value则是 线程通过Hibernate ThreadLocal.set方法保存的对象实例。当线程调用Hibernate ThreadLocal.get方法时, Hibernate ThreadLocal会根据当前线程对象的引用,取出Map中对应的对象返回。

         这样,Hibernate ThreadLocal通过以各个线程对象的引用作为区分,从而将不同线程的变量隔离开来。

Hibernate官方开发手册标准示例:

 
 
  1. public class HibernateUtil {   
  2. private static SessionFactory sessionFactory;  
  3. static {   
  4. try {   
  5. // Create the SessionFactory sessionFactory = new Configuration(). configure().buildSessionFactory();  
  6. }   
  7. catch (HibernateException ex) {   
  8. throw new RuntimeException( "Configuration problem: " + ex.getMessage(), ex );  
  9. }   
  10. }   
  11. public static final ThreadLocal session = new ThreadLocal();  
  12. public static Session currentSession() throws HibernateException {   
  13. Session s = (Session) session.get();  
  14. // Open a new Session, if this Thread has none yet if (s == null) {   
  15. s = sessionFactory.openSession();  
  16. session.set(s);  
  17. }   
  18. return s;  
  19. }   
  20. public static void closeSession() throws HibernateException {   
  21. Session s = (Session) session.get();  
  22. session.set(null);  
  23. if (s != null) s.close();  
  24. }   

通过filter实现session的重用:

 
 
  1. public class PersistenceFilter implements Filter {   
  2. protected static ThreadLocal hibernateHolder = new ThreadLocal();  
  3. public void doFilter(ServletRequest request,  ServletResponse response, FilterChain chain) 
  4. throws IOException,ServletException {   
  5. hibernateHolder.set(getSession());  
  6. try {   
  7. ……   
  8. chain.doFilter(request, response);  
  9. ……   
  10. }   
  11. finally {   
  12. Session sess = (Session)hibernateHolder.get();  
  13. if (sess != null) { hibernateHolder.set(null);  
  14. try { sess.close(); } catch (HibernateException ex) {   
  15. throw new ServletException(ex);  
  16. }   
  17. }   
  18. }   
  19. }   
  20. ……  


         在 Web 层进行延迟加载 幸运的是, Spring 框架为 Hibernate 延迟加载与 DAO 模式的整合提供了一种方便的解决方法。对那些不熟悉 Spring Hibernate 集成使用的人,我不会在这里讨论过多的细节,但是我建议你去了解 Hibernate Spring 集成的数据访问。以一个 Web 应用为例, Spring 提供了 OpenSessionInViewFilter OpenSessionInViewInterceptor 。我们可以随意选择一个类来实现相同的功能。两种方法唯一的不同就在于 interceptor Spring 容器中运行并被配置在 web 应用的上下文中,而 Filter Spring 之前运行并被配置在 web.xml 中。不管用哪个,他们都在请求将当前会话与当前(数据库)线程绑定时打开 Hibernate 会话。一旦已绑定到线程,这个打开了的 Hibernate 会话可以在 DAO 实现类中透明地使用。这个会话会为延迟加载数据库中值对象的视图保持打开状态。一旦这个逻辑视图完成了, Hibernate 会话会在 Filter doFilter 方法或者 Interceptor postHandle 方法中被关闭。下面是每个组件的配置示例:


一、什么是OpenSessionInView

  在hibernate中使用load方法时,并未把数据真正获取时就关闭了session,当我们真正想获取数据时会迫使load加载数据,而此时session已关闭,所以就会出现异常。 比较典型的是在MVC模式中,我们在M层调用持久层获取数据时(持久层用的是load方法加载数据),当这一调用结束时,session随之关闭,而我们希望在V层使用这些数据,这时才会迫使load加载数据,我们就希望这时的session是open着得,这就是所谓的Open Session In view

 

原理如下:

 我们可以filter来达到此目的。下面的代码初步解决了此问题:

  public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain){

  try{

  Session session = ...SessionFactory.getCurrentSeesion(); //得到session对象

  tx = session.beginTransaction(); //开启事务

  chain.doFliter(request,response); //传递给下一个

  tx.commit(); //提交事务

  }catch (Exception e){

  //出现异常,回滚事务

  }

  }

  简析:当请求到达时,会首先被此拦截器拦截,当数据经过获取并在V层显示完毕后,回到此Filter内部,此时提交事务-->关闭session。

 

为什么会有这么大的提升?

1.之前的SQL是不管用不用,只要是false,全部查出来再说;

2.现在的lazy都是true,在初次查询时,只会查询实体需要的字段,而不会查询关联的对象。当在View中需要关联时,才会去查询数据库。

lazy=true + OpenSessionInView可以避免很多不是必要的查询,速度自然会有所提升。

使用注意事项:

1.在Dao中无需再创建Transcation,因为过滤器已经创建好。

2.使用Session,请使用getCurrentSession来获取,得到当前的Session,而不是openSession,新建一个Session。

3.通过getCurrentSession获得的Session无需关闭,因为在过滤器提交时会自动关闭。

 

  二、spring的osiv解决方案

  OpenSessionInViewFilter [全名:org.springframework.orm.hibernate3.support.OpenSessionInViewFilter]是Spring提供的一个针对Hibernate的一个支持类,其主要意思是在发起一个页面请求时打开Hibernate的Session,一直保持这个Session,直到这个请求结束,具体是通过一个Filter来实现的。

  由于Hibernate引入了Lazy Load特性,使得脱离Hibernate的Session周期的对象如果再想通过getter方法取到其关联对象的值,Hibernate会抛出一个LazyLoad的Exception。所以为了解决这个问题,Spring引入了这个Filter,使得Hibernate的Session的生命周期变长。

  有两种方式可以配置实现OpenSessionInView,分别是OpenSessionInViewInterceptor和OpenSessionInViewFilter,功能完全相同,只不过一个在web.xml配置,另一个在application.xml配置而已。我个人比较倾向配置在application.xml里,因为web.xml里配置的东西的太多的话容易发生冲突,虽然可以调整,但是毕竟多了个麻烦。

  OpenSessionInViewInterceptor配置:

  <beans>

  <bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">

  <property name="sessionFactory">

  <ref bean="sessionFactory"/>

  </property>

  </bean>

  <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

  <property name="interceptors">

  <list>

  <ref bean="openSessionInViewInterceptor"/>

  </list>

  </property>

  <property name="mappings">

  ......

  </property>

  </bean>

  ......

  </beans>

  

 

 

OpenSessionInViewFilter配置: (此监听器应该在struts2的监听器前面)

 

 

  <web-app>

  ......

  <filter>

  <filter-name>hibernateFilter</filter-name>

  <filter-class>

  org.springframework.orm.hibernate3.support.OpenSessionInViewFilter

  </filter-class>

  <init-param>

  <param-name>singleSession</param-name>

  <param-value>true</param-value>

  </init-param>

  </filter>

  ......

  <filter-mapping>

  <filter-name>hibernateFilter</filter-name>

  <url-pattern>*.do</url-pattern>

  </filter-mapping>

  ......

  </web-app>

 

 

Spring 为我们提供了一个叫做 OpenSessionInViewFilter 的过滤器,他是标准的 Servlet Filter 所以我们把它按照规范配置到 web.xml 中方可使用。使用中我们必须配合使用 Spring 的 HibernateDaoSupport 来进行开发,也就是说,我们的dao层的类都要继承于 HibernateDaoSupport,从中由 Spring 来控制 Hibernate 的 Session 在请求来的时候开启,走的时候关闭,保证了我们访问数据对象时的稳定性。

  1. 在 web.xml 中加入对应过滤器配置文件

代码
复制代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />--> <!-- Spring的OpenSessionInView实现 -->
<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>/*</url-pattern>
</filter-mapping>
复制代码

 

  

  2. 在我们访问持久层数据是使用 Spring 为我们的 HibernateDaoSupport 的支持,并使用其中的对应方法操作我们的持久层数据

代码
复制代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />--> import  org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class XxxDAO extends
 HibernateDaoSupport {

    
public void
 save(Xxx transientInstance) {
        
try
 {
            getHibernateTemplate().save(transientInstance);
        } 
catch
 (RuntimeException re) {
            
throw
 re;
        }
    }
}
复制代码

   OpenSessionInViewFilter的主要功能是用来把一个Hibernate Session和一次完整的请求过程对应的线程相绑定。Open Session In View在request把session绑定到当前thread期间一直保持hibernate session在open状态,使session在request的整个期间都可以使用,如在View层里PO也可以lazy loading数据,如 ${ company.employees }。当View 层逻辑完成后,才会通过Filter的doFilter方法或Interceptor的postHandle方法自动关闭session。

 

  很多人在使用OpenSessionInView过程中提及一个错误:


org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER) – turn your Session into FlushMode.AUTO or remove ‘readOnly’ marker from transaction definition


  看看OpenSessionInViewFilter里的几个方法:

 

代码
复制代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />--> protected void  doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)
 
throws
 ServletException, IOException {
 
 SessionFactory sessionFactory 
=
 lookupSessionFactory(); 
 logger.debug(
"Opening Hibernate Session in OpenSessionInViewFilter"
); 
 Session session 
=
 getSession(sessionFactory); 
 TransactionSynchronizationManager.bindResource( 
 sessionFactory, 
new
 SessionHolder(session)); 

  
try
 {
 
    filterChain.doFilter(request, response);
 
  } 
  
finally
 {
 
   TransactionSynchronizationManager.unbindResource(sessionFactory); 
   logger.debug(
"Closing Hibernate Session in OpenSessionInViewFilter"
); 
   closeSession(session, sessionFactory);
 
 }
 
}
复制代码

 

 

 

代码
复制代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />--> protected  Session getSession(SessionFactory sessionFactory)
 
throws
 DataAccessResourceFailureException {
 
Session session 
= SessionFactoryUtils.getSession(sessionFactory, true
);
 
  session.setFlushMode(FlushMode.NEVER);
 
  
return
 session;
 
复制代码

 

 

代码
复制代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />--> protected void  closeSession(Session session, SessionFactory sessionFactory)
 
throws
 CleanupFailureDataAccessException {
 
  SessionFactoryUtils.closeSessionIfNecessary(session, sessionFactory);
 
}
复制代码

 

   可以看到OpenSessionInViewFilter在getSession的时候,会把获取回来的session的flush mode 设为FlushMode.NEVER。然后把该sessionFactory绑定到 TransactionSynchronizationManager,使request的整个过程都使用同一个session,在请求过后再解除该 sessionFactory的绑定,最后closeSessionIfNecessary根据该 session是否已和transaction绑定来决定是否关闭session。在这个过程中,若HibernateTemplate 发现自当前session有不是readOnly的transaction,就会获取到FlushMode.AUTO Session,使方法拥有写权限。也即是,如果有不是readOnly的transaction就可以由Flush.NEVER转为Flush.AUTO,拥有 insert,update,delete操作权限,如果没有transaction,并且没有另外人为地设flush model的话,则doFilter的整个过程都是Flush.NEVER。所以受transaction保护的方法有写权限,没受保护的则没有。

 

  从上述代码其实可以得到一些对我们的开发有帮助的结论:
  1)如果使用了OpenSessionInView模式,那么Spring会帮助你管理Session的开和关,从而你在你的DAO中通过HibernateDaoSupport拿到的getSession()方法,都是绑定到当前线程的线程安全的Session,即拿即用,最后会由Filter统一关闭。
  2)由于拿到的Hibernate的Session被设置了session.setFlushMode(FlushMode.NEVER); 所以,除非你直接调用session.flush(),否则Hibernate session无论何时也不会flush任何的状态变化到数据库。因此,数据库事务的配置非常重要。(我们知道,在调用org.hibernate.Transaction.commit()的时候会触发session.flush())我曾经见过很多人在使用OpenSessionInView模式时,都因为没有正确配置事务,导致了底层会抛出有关FlushMode.NEVER的异常。

 

  总结:

  OpenSessionInView这个模式使用比较简单,也成为了大家在Web开发中经常使用的方法,不过它有时候会带来一些意想不到的问题,这也是在开发中需要注意的。
  1. 在Struts+Spring+Hibernate环境中,由于配置的问题导致的模式失效这个问题以前论坛曾经讨论过,可以参考一下下面这个帖子:
http://www.javaeye.com/topic/15057

  2. OpenSessionInView的效率问题
  这个问题也有人在论坛提出过,Robbin曾经做过具体的测试,可以具体参考一下下面这个帖子:
http://www.javaeye.com/topic/17501

  3. 由于使用了OpenSessionInView模式后造成了内存和数据库连接问题
  这个问题是我在生产环境中碰到的一个问题。由于使用了OpenSessionInView模式,Session的生命周期变得非常长。虽然解决了Lazy Load的问题,但是带来的问题就是Hibernate的一级缓存,也就是Session级别的缓存的生命周期会变得非常长,那么如果你在你的Service层做大批量的数据操作时,其实这些数据会在缓存中保留一份,这是非常耗费内存的。还有一个数据库连接的问题,存在的原因在于由于数据库的Connection是和Session绑在一起的,所以,Connection也会得不到及时的释放。因而当系统出现业务非常繁忙,而计算量又非常大的时候,往往数据连接池的连接数会不够。这个问题我至今非常头痛,因为有很多客户对数据连接池的数量会有限制,不会给你无限制的增加下去。

  4. 使用了OpenSessionInView模式以后取数据的事务问题
  在使用了OpenSessionInView以后,其实事务的生命周期比Session的生命周期来得短,就以为着,其实有相当一部分的查询是不被纳入到事务的范围内的,此时是否会读到脏数据?这个问题我至今不敢确认,有经验的朋友请指教一下。

  最后提一下OpenSessionInView模式的一些替代方案,可以使用OpenSessionInViewInterceptor来代替这个Filter,此时可以使用Spring的AOP配置,将这个Interceptor配置到你所需要的层次上去。另外就是只能使用最古老的Hibernate.initialize()方法进行初始化了。

 

转载自:

 OpenSessionInViewFilter作用及配置:http://www.yybean.com/opensessioninviewfilter-role-and-configuration

OpenSessionInView详解:http://www.javaeye.com/topic/32001

你可能感兴趣的:(spring,Hibernate,延迟加载,osiv)