用 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 不久, 欢迎各位拍转

 
相关文章
|
1月前
|
XML 安全 Java
|
2月前
|
缓存 NoSQL Java
什么是缓存?如何在 Spring Boot 中使用缓存框架
什么是缓存?如何在 Spring Boot 中使用缓存框架
59 0
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
3月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
61 0
|
12天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
8天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
35 13
|
20天前
|
IDE Java 测试技术
互联网应用主流框架整合之Spring Boot开发
通过本文的介绍,我们详细探讨了Spring Boot开发的核心概念和实践方法,包括项目结构、数据访问层、服务层、控制层、配置管理、单元测试以及部署与运行。Spring Boot通过简化配置和强大的生态系统,使得互联网应用的开发更加高效和可靠。希望本文能够帮助开发者快速掌握Spring Boot,并在实际项目中灵活应用。
37 5
|
30天前
|
缓存 Java 数据库连接
Spring框架中的事件机制:深入理解与实践
Spring框架是一个广泛使用的Java企业级应用框架,提供了依赖注入、面向切面编程(AOP)、事务管理、Web应用程序开发等一系列功能。在Spring框架中,事件机制是一种重要的通信方式,它允许不同组件之间进行松耦合的通信,提高了应用程序的可维护性和可扩展性。本文将深入探讨Spring框架中的事件机制,包括不同类型的事件、底层原理、应用实践以及优缺点。
64 8
|
2月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
89 6
|
2月前
|
Java 数据库连接 数据库
不可不知道的Spring 框架七大模块
Spring框架是一个全面的Java企业级应用开发框架,其核心容器模块为其他模块提供基础支持,包括Beans、Core、Context和SpEL四大子模块;数据访问及集成模块支持数据库操作,涵盖JDBC、ORM、OXM、JMS和Transactions;Web模块则专注于Web应用,提供Servlet、WebSocket等功能;此外,还包括AOP、Aspects、Instrumentation、Messaging和Test等辅助模块,共同构建强大的企业级应用解决方案。
110 2