众所周知, 为了解决 Hibernate Lazy 问题, Spring 中引入了 OpenSessionInViewInterceptor, 这样虽然解决了页面上的 Lazy Load 问题,却增加了各层之间的偶合性,
如果一个 Lazy 的 Collection 在页面上可以被正确的 load, 但是如果请求不是来自于 HttpServletRequest (比如在 TestCase 或 Service 中希望获取 lazy 的属性),
一般会导致两种错误:
代码
- 1. 设置了 lazy = "true"
- 会导致 org.hibernate.LazyInitializationException: failed to lazily initialize a collection of xxx: xxx - no session or session was closed
- 2. 设置里 lazy = "false"
- 会导致 org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
为了方便测试, 灵活使用 lazy load, 我按照 OpenSessionInViewInterceptor 的思路实现了一个 HibernateLazyResolber, 代码如下:
代码
- /*
- * Copyright 2004-2005 wangz.
- * Project shufe_newsroom
- */
- package org.summerfragrance.support.hibernate3;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.hibernate.FlushMode;
- import org.hibernate.Session;
- import org.hibernate.SessionFactory;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.dao.DataAccessResourceFailureException;
- import org.springframework.orm.hibernate3.SessionFactoryUtils;
- import org.springframework.orm.hibernate3.SessionHolder;
- import org.springframework.transaction.support.TransactionSynchronizationManager;
- /**
- * <class>HibernateLazyResolver</class> 用于模拟 OpenSessionInViewInterceptor, 它可以被任意使用而不依赖于 Web 环境
- *
- * @see org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor
- * @see org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
- * @since 2005-7-14
- * @author 王政
- * @version $Id: HibernateLazyResolver.java,v 1.4 2005/07/14 14:15:19 Administrator Exp $
- */
- public class HibernateLazyResolver implements InitializingBean {
- private static Log logger = LogFactory.getLog(HibernateLazyResolver.class);
- private boolean singleSession = true;
- private SessionFactory sessionFactory;
- boolean participate = false;
- protected Session session = null;
- public final void setSessionFactory(SessionFactory sessionFactory) {
- this.sessionFactory = sessionFactory;
- }
- /**
- * Set whether to use a single session for each request. Default is true.
- * <p>If set to false, each data access operation or transaction will use
- * its own session (like without Open Session in View). Each of those
- * sessions will be registered for deferred close, though, actually
- * processed at request completion.
- * @see SessionFactoryUtils#initDeferredClose
- * @see SessionFactoryUtils#processDeferredClose
- */
- public void setSingleSession(boolean singleSession) {
- this.singleSession = singleSession;
- }
- /**
- * Return whether to use a single session for each request.
- */
- protected boolean isSingleSession() {
- return singleSession;
- }
- public void afterPropertiesSet() throws Exception {
- if (sessionFactory == null) {
- throw new IllegalArgumentException("SessionFactory is reqirued!");
- }
- }
- /**
- * 初始化 session, 在需要 lazy 的开始处调用
- *
- */
- public void openSession() {
- if (isSingleSession()) {
- // single session mode
- if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
- // Do not modify the Session: just set the participate flag.
- participate = true;
- }
- else {
- logger.debug("Opening single Hibernate Session in HibernateLazyResolver");
- session = getSession(sessionFactory);
- TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
- }
- }
- else {
- // deferred close mode
- if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
- // Do not modify deferred close: just set the participate flag.
- participate = true;
- }
- else {
- SessionFactoryUtils.initDeferredClose(sessionFactory);
- }
- }
- }
- /**
- * 释放 session, 在 lazy 的结束处调用
- *
- */
- public void releaseSession() {
- if (!participate) {
- if (isSingleSession()) {
- // single session mode
- TransactionSynchronizationManager.unbindResource(sessionFactory);
- logger.debug("Closing single Hibernate Session in HibernateLazyResolver");
- try {
- closeSession(session, sessionFactory);
- }
- catch (RuntimeException ex) {
- logger.error("Unexpected exception on closing Hibernate Session", ex);
- }
- }
- else {
- // deferred close mode
- SessionFactoryUtils.processDeferredClose(sessionFactory);
- }
- }
- }
- /**
- * Get a Session for the SessionFactory that this filter uses.
- * Note that this just applies in single session mode!
- * <p>The default implementation delegates to SessionFactoryUtils'
- * getSession method and sets the Session's flushMode to NEVER.
- * <p>Can be overridden in subclasses for creating a Session with a custom
- * entity interceptor or JDBC exception translator.
- * @param sessionFactory the SessionFactory that this filter uses
- * @return the Session to use
- * @throws DataAccessResourceFailureException if the Session could not be created
- * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
- * @see org.hibernate.FlushMode#NEVER
- */
- protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
- Session session = SessionFactoryUtils.getSession(sessionFactory, true);
- // 注意这里与 OpenSessionInViewInterceptor 不同, 需要设置为 auto, 否则会导致以下异常
- // 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
- session.setFlushMode(FlushMode.AUTO);
- return session;
- }
- /**
- * Close the given Session.
- * Note that this just applies in single session mode!
- * <p>The default implementation delegates to SessionFactoryUtils'
- * releaseSession method.
- * <p>Can be overridden in subclasses, e.g. for flushing the Session before
- * closing it. See class-level javadoc for a discussion of flush handling.
- * Note that you should also override getSession accordingly, to set
- * the flush mode to something else than NEVER.
- * @param session the Session used for filtering
- * @param sessionFactory the SessionFactory that this filter uses
- */
- protected void closeSession(Session session, SessionFactory sessionFactory) {
- // 需要 flush session
- session.flush();
- SessionFactoryUtils.releaseSession(session, sessionFactory);
- }
- }
使用方法, 在配置文件中声明
代码
- <!-- use to resolve hibernate lazy load -->
- <bean id="hibernateLazyResolver" class="org.summerfragrance.support.hibernate3.HibernateLazyResolver">
- <property name="sessionFactory"><ref local="sessionFactory"/></property>
- </bean>
- <bean id="userManager" parent="txProxyTemplate">
- <property name="target">
- <bean class="org.summerfragrance.security.service.impl.UserManagerImpl" parent="managerTarget">
- <property name="userDAO"><ref bean="userDAO"/></property>
- <property name="hibernateLazyResolver"><ref bean="hibernateLazyResolver"/></property>
- </bean>
- </property>
- </bean>
然后在代码中这样调用
代码
- hibernateLazyResolver.openSession();
- ...
- //需要 lazy load 的代码
- hibernateLazyResolver.releaseSession();
如果是 TestCase, 可以简单的设置 BaseTestCase 如下
代码
- package org.summerfragrance;
- import junit.framework.TestCase;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- import org.summerfragrance.support.hibernate3.HibernateLazyResolver;
- /**
- * Base class for running DAO tests.
- *
- * @author mraible
- */
- public class BaseTestCase extends TestCase {
- protected final Log log = LogFactory.getLog(getClass());
- protected final static ApplicationContext ctx;
- protected HibernateLazyResolver hibernateLazyResolver;
- static {
- String[] paths = { "/conf/applicationContext-dataSource.xml",
- "/org/summerfragrance/vfs/applicationContext-vfs.xml",
- "/org/summerfragrance/security/dao/hibernate/applicationContext-hibernate.xml"
- // "/org/summerfragrance/security/dao/jdbc/applicationContext-jdbc.xml"
- };
- ctx = new ClassPathXmlApplicationContext(paths);
- }
- /**
- * @see junit.framework.TestCase#setUp()
- */
- protected void setUp() throws Exception {
- super.setUp();
- hibernateLazyResolver = (HibernateLazyResolver) ctx
- .getBean("hibernateLazyResolver");
- hibernateLazyResolver.openSession();
- }
- /**
- * @see junit.framework.TestCase#tearDown()
- */
- protected void tearDown() throws Exception {
- super.tearDown();
- hibernateLazyResolver.releaseSession();
- hibernateLazyResolver = null;
- }
- }
这样就可以在 Service 和 TestCase 中使用 Lazy Load 了, 目前已经测试通过
这几天看 JavaEye 上关于 OpenSessionInView 的讨论, 感觉这个问题比较常见
代码
- 在代码中调用 openSession(), 然后不予处理, 这就是 ajoo 说的第一种不擦屁股就直接走人的做法, 这样可能导致两种错误;
- a. org.springframework.orm.hibernate3.HibernateSystemException: Illegal attempt to associate a collection with two open sessions
- b. 数据库连接不关闭
- 正确的做法是用 HibernateCallBack 或者参照 HibernateTemplate 对 session 进行处理
以上都是一些个人想法, 小弟学习 Hibernate 不久, 欢迎各位拍转