用 OpenSessionInViewInterceptor 的思路解决 Spring框架中的Hib

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

代码

 

[java] view plaincopy

  1. 01.1. 设置了 lazy = "true"    

  2. 02.   会导致 org.hibernate.LazyInitializationException: failed to lazily initialize a collection of xxx: xxx - no session or session was closed     

  3. 03.2. 设置里 lazy = "false"    

  4. 04.   会导致 org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed     


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

 

[java] view plaincopy

  1. package cn.ccut312.common.daobase;  

  2.   

  3.     

  4. import org.apache.commons.logging.Log;     

  5. import org.apache.commons.logging.LogFactory;     

  6. import org.hibernate.FlushMode;     

  7. import org.hibernate.Session;     

  8. import org.hibernate.SessionFactory;     

  9. import org.springframework.beans.factory.InitializingBean;     

  10. import org.springframework.dao.DataAccessResourceFailureException;     

  11. import org.springframework.orm.hibernate3.SessionFactoryUtils;  

  12. import org.springframework.orm.hibernate3.SessionHolder;  

  13. import org.springframework.transaction.support.TransactionSynchronizationManager;     

  14.     

  15. /**    

  16.  * <class>HibernateLazyResolver</class> 用于模拟 OpenSessionInViewInterceptor, 它可以被任意使用而不依赖于 Web 环境    

  17.  *     

  18.  * @see org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor    

  19.  * @see org.springframework.orm.hibernate.support.OpenSessionInViewFilter    

  20.  * @since --    

  21.  * @author 王政    

  22.  * @version $Id: HibernateLazyResolver.java,v . // :: Administrator Exp $    

  23.  */     

  24. public class HibernateLazyResolver implements InitializingBean {     

  25.     

  26.     private static Log logger = LogFactory.getLog(HibernateLazyResolver.class);     

  27.     

  28.     private boolean singleSession = true;      

  29.     

  30.     private SessionFactory sessionFactory;     

  31.     

  32.     boolean participate = false;     

  33.     

  34.     protected Session session = null;     

  35.             

  36.     public final void setSessionFactory(SessionFactory sessionFactory) {     

  37.         this.sessionFactory = sessionFactory;     

  38.     }     

  39.     

  40.     /**    

  41.     * Set whether to use a single session for each request. Default is true.    

  42.     * <p>If set to false, each data access operation or transaction will use    

  43.     * its own session (like without Open Session in View). Each of those    

  44.     * sessions will be registered for deferred close, though, actually    

  45.     * processed at request completion.    

  46.     * @see SessionFactoryUtils#initDeferredClose    

  47.     * @see SessionFactoryUtils#processDeferredClose    

  48.     */     

  49.     public void setSingleSession(boolean singleSession) {     

  50.         this.singleSession = singleSession;     

  51.     }     

  52.     

  53.     /**    

  54.     * Return whether to use a single session for each request.    

  55.     */     

  56.     protected boolean isSingleSession() {     

  57.         return singleSession;     

  58.     }     

  59.          

  60.     public void afterPropertiesSet() throws Exception {     

  61.         if (sessionFactory == null) {     

  62.             throw new IllegalArgumentException("SessionFactory is reqirued!");     

  63.         }     

  64.     }     

  65.     

  66.     /**    

  67.      * 初始化 session, 在需要 lazy 的开始处调用    

  68.      *    

  69.      */     

  70.     public void openSession() {     

  71.         if (isSingleSession()) {     

  72.             // single session mode     

  73.             if (TransactionSynchronizationManager.hasResource(sessionFactory)) {     

  74.                 // Do not modify the Session: just set the participate flag.     

  75.                 participate = true;     

  76.             }     

  77.             else {     

  78.                 logger.debug("Opening single Hibernate Session in HibernateLazyResolver");     

  79.                 session = getSession(sessionFactory);     

  80.                 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));     

  81.             }     

  82.         }     

  83.         else {     

  84.             // deferred close mode     

  85.             if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {     

  86.                 // Do not modify deferred close: just set the participate flag.     

  87.                 participate = true;     

  88.             }     

  89.             else {     

  90.                 SessionFactoryUtils.initDeferredClose(sessionFactory);     

  91.             }     

  92.         }     

  93.              

  94.     }     

  95.     

  96.     /**    

  97.      * 释放 session, 在 lazy 的结束处调用    

  98.      *    

  99.      */     

  100.     public void releaseSession() {     

  101.         if (!participate) {     

  102.             if (isSingleSession()) {     

  103.                // single session mode     

  104.                 TransactionSynchronizationManager.unbindResource(sessionFactory);     

  105.                 logger.debug("Closing single Hibernate Session in HibernateLazyResolver");     

  106.                 try {     

  107.                     closeSession(session, sessionFactory);     

  108.                 }     

  109.                 catch (RuntimeException ex) {     

  110.                     logger.error("Unexpected exception on closing Hibernate Session", ex);     

  111.                 }     

  112.             }     

  113.             else {     

  114.                 // deferred close mode     

  115.                 SessionFactoryUtils.processDeferredClose(sessionFactory);     

  116.             }     

  117.         }     

  118.     }     

  119.              

  120.     /**    

  121.      * Get a Session for the SessionFactory that this filter uses.    

  122.      * Note that this just applies in single session mode!    

  123.      * <p>The default implementation delegates to SessionFactoryUtils'    

  124.      * getSession method and sets the Session's flushMode to NEVER.    

  125.      * <p>Can be overridden in subclasses for creating a Session with a custom    

  126.      * entity interceptor or JDBC exception translator.    

  127.      * @param sessionFactory the SessionFactory that this filter uses    

  128.      * @return the Session to use    

  129.      * @throws DataAccessResourceFailureException if the Session could not be created    

  130.      * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory, boolean)    

  131.      * @see org.hibernate.FlushMode#NEVER    

  132.      */     

  133.     protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {     

  134.         Session session = SessionFactoryUtils.getSession(sessionFactory, true);     

  135.         // 注意这里与 OpenSessionInViewInterceptor 不同, 需要设置为 auto, 否则会导致以下异常     

  136.         // org.springframework.dao.InvalidDataAccessApiUsageException:      

  137.         // Write operations are not allowed in read-only mode (FlushMode.NEVER) -      

  138.         // turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition     

  139.         session.setFlushMode(FlushMode.AUTO);     

  140.         return session;     

  141.     }     

  142.     

  143.     /**    

  144.      * Close the given Session.    

  145.      * Note that this just applies in single session mode!    

  146.      * <p>The default implementation delegates to SessionFactoryUtils'    

  147.      * releaseSession method.    

  148.      * <p>Can be overridden in subclasses, e.g. for flushing the Session before    

  149.      * closing it. See class-level javadoc for a discussion of flush handling.    

  150.      * Note that you should also override getSession accordingly, to set    

  151.      * the flush mode to something else than NEVER.    

  152.      * @param session the Session used for filtering    

  153.      * @param sessionFactory the SessionFactory that this filter uses    

  154.      */     

  155.     protected void closeSession(Session session, SessionFactory sessionFactory) {    

  156.         session.flush();  

  157.         SessionFactoryUtils.releaseSession(session, sessionFactory);  

  158.     }     

  159. }     

 

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

 

代码

 

[html] view plaincopy

  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>    


然后在代码中这样调用

 

[java] view plaincopy

  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.   }     

 

这样就可以在 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 不久, 欢迎各位拍转

 

 

代码


你可能感兴趣的:(用 OpenSessionInViewInterceptor 的思路解决 Spring框架中的Hib)