用 OpenSessionInViewInterceptor 的思路解决 Spring框架中的Hibernate Lazy

简介: 众所周知, 为了解决 Hibernate Lazy 问题, Spring 中引入了 OpenSessionInViewInterceptor, 这样虽然解决了页面上的 Lazy Load 问题,却增加了各层之间的偶合性, 如果一个 Lazy 的 Collection 在页面上可以被正确的 load, 但是如果请求不是来自于 HttpServletRequest (比如在 TestCase 或

众所周知, 为了解决 Hibernate Lazy 问题, Spring 中引入了 OpenSessionInViewInterceptor, 这样虽然解决了页面上的 Lazy Load 问题,却增加了各层之间的偶合性,
如果一个 Lazy 的 Collection 在页面上可以被正确的 load, 但是如果请求不是来自于 HttpServletRequest (比如在 TestCase 或 Service 中希望获取 lazy 的属性),
一般会导致两种错误:

代码
  1. 1. 设置了 lazy = "true"  
  2.    会导致 org.hibernate.LazyInitializationException: failed to lazily initialize a collection of xxx: xxx - no session or session was closed   
  3. 2. 设置里 lazy = "false"  
  4.    会导致 org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed   

 

为了方便测试, 灵活使用 lazy load, 我按照 OpenSessionInViewInterceptor 的思路实现了一个 HibernateLazyResolber, 代码如下:

 

代码
  1.   
  2. /*   
  3.  * Copyright 2004-2005 wangz.   
  4.  * Project shufe_newsroom   
  5.  */   
  6. package org.summerfragrance.support.hibernate3;   
  7.   
  8. import org.apache.commons.logging.Log;   
  9. import org.apache.commons.logging.LogFactory;   
  10. import org.hibernate.FlushMode;   
  11. import org.hibernate.Session;   
  12. import org.hibernate.SessionFactory;   
  13. import org.springframework.beans.factory.InitializingBean;   
  14. import org.springframework.dao.DataAccessResourceFailureException;   
  15. import org.springframework.orm.hibernate3.SessionFactoryUtils;   
  16. import org.springframework.orm.hibernate3.SessionHolder;   
  17. import org.springframework.transaction.support.TransactionSynchronizationManager;   
  18.   
  19. /**   
  20.  * <class>HibernateLazyResolver</class> 用于模拟 OpenSessionInViewInterceptor, 它可以被任意使用而不依赖于 Web 环境   
  21.  *    
  22.  * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor   
  23.  * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter   
  24.  * @since 2005-7-14   
  25.  * @author 王政   
  26.  * @version $Id: HibernateLazyResolver.java,v 1.4 2005/07/14 14:15:19 Administrator Exp $   
  27.  */   
  28. public class HibernateLazyResolver implements InitializingBean {   
  29.   
  30.     private static Log logger = LogFactory.getLog(HibernateLazyResolver.class);   
  31.   
  32.     private boolean singleSession = true;    
  33.   
  34.     private SessionFactory sessionFactory;   
  35.   
  36.     boolean participate = false;   
  37.   
  38.     protected Session session = null;   
  39.           
  40.     public final void setSessionFactory(SessionFactory sessionFactory) {   
  41.         this.sessionFactory = sessionFactory;   
  42.     }   
  43.   
  44.     /**   
  45.     * Set whether to use a single session for each request. Default is true.   
  46.     * <p>If set to false, each data access operation or transaction will use   
  47.     * its own session (like without Open Session in View). Each of those   
  48.     * sessions will be registered for deferred close, though, actually   
  49.     * processed at request completion.   
  50.     * @see SessionFactoryUtils#initDeferredClose   
  51.     * @see SessionFactoryUtils#processDeferredClose   
  52.     */   
  53.     public void setSingleSession(boolean singleSession) {   
  54.         this.singleSession = singleSession;   
  55.     }   
  56.   
  57.     /**   
  58.     * Return whether to use a single session for each request.   
  59.     */   
  60.     protected boolean isSingleSession() {   
  61.         return singleSession;   
  62.     }   
  63.        
  64.     public void afterPropertiesSet() throws Exception {   
  65.         if (sessionFactory == null) {   
  66.             throw new IllegalArgumentException("SessionFactory is reqirued!");   
  67.         }   
  68.     }   
  69.   
  70.     /**   
  71.      * 初始化 session, 在需要 lazy 的开始处调用   
  72.      *   
  73.      */   
  74.     public void openSession() {   
  75.         if (isSingleSession()) {   
  76.             // single session mode   
  77.             if (TransactionSynchronizationManager.hasResource(sessionFactory)) {   
  78.                 // Do not modify the Session: just set the participate flag.   
  79.                 participate = true;   
  80.             }   
  81.             else {   
  82.                 logger.debug("Opening single Hibernate Session in HibernateLazyResolver");   
  83.                 session = getSession(sessionFactory);   
  84.                 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));   
  85.             }   
  86.         }   
  87.         else {   
  88.             // deferred close mode   
  89.             if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {   
  90.                 // Do not modify deferred close: just set the participate flag.   
  91.                 participate = true;   
  92.             }   
  93.             else {   
  94.                 SessionFactoryUtils.initDeferredClose(sessionFactory);   
  95.             }   
  96.         }   
  97.            
  98.     }   
  99.   
  100.     /**   
  101.      * 释放 session, 在 lazy 的结束处调用   
  102.      *   
  103.      */   
  104.     public void releaseSession() {   
  105.         if (!participate) {   
  106.             if (isSingleSession()) {   
  107.                 // single session mode   
  108.                 TransactionSynchronizationManager.unbindResource(sessionFactory);   
  109.                 logger.debug("Closing single Hibernate Session in HibernateLazyResolver");   
  110.                 try {   
  111.                     closeSession(session, sessionFactory);   
  112.                 }   
  113.                 catch (RuntimeException ex) {   
  114.                     logger.error("Unexpected exception on closing Hibernate Session", ex);   
  115.                 }   
  116.             }   
  117.             else {   
  118.                 // deferred close mode   
  119.                 SessionFactoryUtils.processDeferredClose(sessionFactory);   
  120.             }   
  121.         }   
  122.     }   
  123.            
  124.     /**   
  125.      * Get a Session for the SessionFactory that this filter uses.   
  126.      * Note that this just applies in single session mode!   
  127.      * <p>The default implementation delegates to SessionFactoryUtils'   
  128.      * getSession method and sets the Session's flushMode to NEVER.   
  129.      * <p>Can be overridden in subclasses for creating a Session with a custom   
  130.      * entity interceptor or JDBC exception translator.   
  131.      * @param sessionFactory the SessionFactory that this filter uses   
  132.      * @return the Session to use   
  133.      * @throws DataAccessResourceFailureException if the Session could not be created   
  134.      * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)   
  135.      * @see org.hibernate.FlushMode#NEVER   
  136.      */   
  137.     protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {   
  138.         Session session = SessionFactoryUtils.getSession(sessionFactory, true);   
  139.         // 注意这里与 OpenSessionInViewInterceptor 不同, 需要设置为 auto, 否则会导致以下异常   
  140.         // org.springframework.dao.InvalidDataAccessApiUsageException:    
  141.         // Write operations are not allowed in read-only mode (FlushMode.NEVER) -    
  142.         // turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition   
  143.         session.setFlushMode(FlushMode.AUTO);   
  144.         return session;   
  145.     }   
  146.   
  147.     /**   
  148.      * Close the given Session.   
  149.      * Note that this just applies in single session mode!   
  150.      * <p>The default implementation delegates to SessionFactoryUtils'   
  151.      * releaseSession method.   
  152.      * <p>Can be overridden in subclasses, e.g. for flushing the Session before   
  153.      * closing it. See class-level javadoc for a discussion of flush handling.   
  154.      * Note that you should also override getSession accordingly, to set   
  155.      * the flush mode to something else than NEVER.   
  156.      * @param session the Session used for filtering   
  157.      * @param sessionFactory the SessionFactory that this filter uses   
  158.      */   
  159.     protected void closeSession(Session session, SessionFactory sessionFactory) {   
  160.         // 需要 flush session   
  161.         session.flush();   
  162.         SessionFactoryUtils.releaseSession(session, sessionFactory);   
  163.     }   
  164. }   

 

使用方法, 在配置文件中声明

 

代码
  1. <!-- use to resolve hibernate lazy load -->  
  2. <bean id="hibernateLazyResolver" class="org.summerfragrance.support.hibernate3.HibernateLazyResolver">  
  3.      <property name="sessionFactory"><ref local="sessionFactory"/></property>  
  4. </bean>    
  5.   
  6. <bean id="userManager" parent="txProxyTemplate">  
  7.        <property name="target">  
  8.            <bean class="org.summerfragrance.security.service.impl.UserManagerImpl" parent="managerTarget">  
  9.                <property name="userDAO"><ref bean="userDAO"/></property>  
  10.             <property name="hibernateLazyResolver"><ref bean="hibernateLazyResolver"/></property>  
  11.            </bean>  
  12.        </property>  
  13.    </bean>  

 

然后在代码中这样调用

 

代码
  1. hibernateLazyResolver.openSession();   
  2.   
  3. ...   
  4. //需要 lazy load 的代码   
  5.   
  6. hibernateLazyResolver.releaseSession();   

 

如果是 TestCase, 可以简单的设置 BaseTestCase 如下

代码
  1.   
  2. package org.summerfragrance;   
  3.   
  4. import junit.framework.TestCase;   
  5.   
  6. import org.apache.commons.logging.Log;   
  7. import org.apache.commons.logging.LogFactory;   
  8. import org.springframework.context.ApplicationContext;   
  9. import org.springframework.context.support.ClassPathXmlApplicationContext;   
  10. import org.summerfragrance.support.hibernate3.HibernateLazyResolver;   
  11.   
  12. /**  
  13.  * Base class for running DAO tests.  
  14.  *   
  15.  * @author mraible  
  16.  */  
  17. public class BaseTestCase extends TestCase {   
  18.   
  19.     protected final Log log = LogFactory.getLog(getClass());   
  20.   
  21.     protected final static ApplicationContext ctx;   
  22.   
  23.     protected HibernateLazyResolver hibernateLazyResolver;   
  24.   
  25.     static {   
  26.         String[] paths = { "/conf/applicationContext-dataSource.xml",   
  27.                 "/org/summerfragrance/vfs/applicationContext-vfs.xml",   
  28.                 "/org/summerfragrance/security/dao/hibernate/applicationContext-hibernate.xml"  
  29.         // "/org/summerfragrance/security/dao/jdbc/applicationContext-jdbc.xml"   
  30.         };   
  31.         ctx = new ClassPathXmlApplicationContext(paths);   
  32.     }   
  33.   
  34.     /**  
  35.      * @see junit.framework.TestCase#setUp()  
  36.      */  
  37.     protected void setUp() throws Exception {   
  38.         super.setUp();   
  39.         hibernateLazyResolver = (HibernateLazyResolver) ctx   
  40.                 .getBean("hibernateLazyResolver");   
  41.         hibernateLazyResolver.openSession();   
  42.     }   
  43.   
  44.     /**  
  45.      * @see junit.framework.TestCase#tearDown()  
  46.      */  
  47.     protected void tearDown() throws Exception {   
  48.         super.tearDown();   
  49.         hibernateLazyResolver.releaseSession();   
  50.         hibernateLazyResolver = null;   
  51.     }   
  52.   
  53. }   
  54.   

 

这样就可以在 Service 和 TestCase 中使用 Lazy Load 了, 目前已经测试通过

这几天看 JavaEye 上关于 OpenSessionInView 的讨论, 感觉这个问题比较常见

 

代码
  1.   
  2.   在代码中调用 openSession(), 然后不予处理, 这就是 ajoo 说的第一种不擦屁股就直接走人的做法, 这样可能导致两种错误;   
  3.    a. org.springframework.orm.hibernate3.HibernateSystemException: Illegal attempt to associate a collection with two open sessions   
  4.    b. 数据库连接不关闭   
  5.    正确的做法是用 HibernateCallBack 或者参照 HibernateTemplate 对 session 进行处理   
  6.   

 

以上都是一些个人想法, 小弟学习 Hibernate 不久, 欢迎各位拍转

 
相关文章
|
8天前
|
存储 安全 Java
事件的力量:探索Spring框架中的事件处理机制
事件的力量:探索Spring框架中的事件处理机制
24 0
|
17天前
|
缓存 Java Spring
Spring 框架中 Bean 的生命周期
Spring 框架中 Bean 的生命周期
29 1
|
30天前
|
网络安全
ssh(Spring+Spring mvc+hibernate)——DeptDaoImpl.java
ssh(Spring+Spring mvc+hibernate)——DeptDaoImpl.java
11 0
|
30天前
|
网络安全
ssh(Spring+Spring mvc+hibernate)——BaseDaoImpl.java
ssh(Spring+Spring mvc+hibernate)——BaseDaoImpl.java
12 0
|
30天前
|
Shell
sh(Spring+Spring mvc+hibernate)——IEmpDao.java
sh(Spring+Spring mvc+hibernate)——IEmpDao.java
11 0
|
30天前
|
Shell
sh(Spring+Spring mvc+hibernate)——IDeptDao.java
sh(Spring+Spring mvc+hibernate)——IDeptDao.java
13 0
|
30天前
|
网络安全
ssh(Spring+Spring mvc+hibernate)——Dept.java
ssh(Spring+Spring mvc+hibernate)——Dept.java
11 0
|
11天前
|
SQL Java 数据库连接
jpa、hibernate、spring-data-jpa、jdbcTemplate
jpa、hibernate、spring-data-jpa、jdbcTemplate
|
30天前
|
网络安全
ssh(Spring+Spring mvc+hibernate)——showDept.jsp
ssh(Spring+Spring mvc+hibernate)——showDept.jsp
8 0
|
30天前
|
网络安全
ssh(Spring+Spring mvc+hibernate)——applicationContext.xml
ssh(Spring+Spring mvc+hibernate)——applicationContext.xml
7 0