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

 
相关文章
|
3天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
15天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
31 4
|
17天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
101 1
|
12天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
27 0
|
7天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
25 1
Spring 框架:Java 开发者的春天
|
1天前
|
缓存 Java 数据库连接
Hibernate:Java持久层框架的高效应用
通过上述步骤,可以在Java项目中高效应用Hibernate框架,实现对关系数据库的透明持久化管理。Hibernate提供的强大功能和灵活配置,使得开发者能够专注于业务逻辑的实现,而不必过多关注底层数据库操作。
5 1
|
7天前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
26天前
|
NoSQL Java Redis
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
这篇文章介绍了Redis的基本命令,并展示了如何使用Netty框架直接与Redis服务器进行通信,包括设置Netty客户端、编写处理程序以及初始化Channel的完整示例代码。
30 1
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
|
13天前
|
人工智能 开发框架 Java
总计 30 万奖金,Spring AI Alibaba 应用框架挑战赛开赛
Spring AI Alibaba 应用框架挑战赛邀请广大开发者参与开源项目的共建,助力项目快速发展,掌握 AI 应用开发模式。大赛分为《支持 Spring AI Alibaba 应用可视化调试与追踪本地工具》和《基于 Flow 的 AI 编排机制设计与实现》两个赛道,总计 30 万奖金。
|
14天前
|
人工智能 Java API
阿里云开源 AI 应用开发框架:Spring AI Alibaba
近期,阿里云重磅发布了首款面向 Java 开发者的开源 AI 应用开发框架:Spring AI Alibaba(项目 Github 仓库地址:alibaba/spring-ai-alibaba),Spring AI Alibaba 项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。本文将详细介绍 Spring AI Alibaba 的核心特性,并通过「智能机票助手」的示例直观的展示 Spring AI Alibaba 开发 AI 应用的便利性。示例源