浏览 33872 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2005-07-14
如果一个 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 不久, 欢迎各位拍转 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2005-07-14
实际项目中往往需要的是Session的Scope跨越多个类方法调用,甚至跨越Web层页面的render。你这种方法需要显式在代码中调用语句来完成Session的打开和关闭,不要说满足不了页面的render,连跨越多个类方法调用的需求都无法满足。
|
|
返回顶楼 | |
发表时间:2005-07-14
robbin 老大这么快就来拍转, 真是惭愧亚 , 不过我觉得这种方法至少可以在某种程度上解决一些问题, 就像我说的 TestCase 和 Service 方法中, 如果需要用到 lazy, 那应该怎么解决呢? 今天也是被郁闷了一天才想到这个办法
|
|
返回顶楼 | |
发表时间:2005-07-14
package com.iteye.common.test; import junit.framework.TestCase; import org.hibernate.FlushMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.orm.hibernate3.SessionFactoryUtils; import org.springframework.orm.hibernate3.SessionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; public abstract class AbstractTestBean extends TestCase { protected ApplicationContext applicationContext; private SessionFactory sessionFactory; private Session session; protected void setUp() throws Exception { String configFile = "spring/*.xml"; applicationContext = new ClassPathXmlApplicationContext(configFile); sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory"); session = SessionFactoryUtils.getSession(sessionFactory, true); session.setFlushMode(FlushMode.NEVER); TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session)); } protected void tearDown() throws Exception { TransactionSynchronizationManager.unbindResource(sessionFactory); SessionFactoryUtils.releaseSession(session, sessionFactory); } } 这是我的TestCase的基类,所有的TestCase继承这个类,就可以具备OpenSessionInView的功能。 |
|
返回顶楼 | |
发表时间:2005-07-15
偶的做法和Robbin几乎是一样的,不过有时候还是需要测试多个session的时候,所以多加了一个newSession()的方法。
public abstract class AbstractTest extends TestCase { protected ApplicationContext context; protected void setUp(); throws Exception { context = getContext();; openSession();; super.setUp();; } protected abstract ApplicationContext getContext();; private void openSession(); { SessionFactory sessionFactory = (SessionFactory); context.getBean("sessionFactory");; Session hibSession = SessionFactoryUtils.getSession(sessionFactory, true);; hibSession.setFlushMode(FlushMode.NEVER);; TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(hibSession););; } protected void newSession(); { closeSession();; openSession();; } protected void tearDown(); throws Exception { super.tearDown();; closeSession();; } private void closeSession(); { SessionFactory sessionFactory = (SessionFactory); context.getBean("sessionFactory");; SessionHolder sessionHolder = (SessionHolder); TransactionSynchronizationManager.unbindResource(sessionFactory);; SessionFactoryUtils.closeSessionIfNecessary(sessionHolder.getSession();, sessionFactory);; } } 如果有需要用到多个session的时候: public void testAbc(); { context.getBean("service");.foo();; newSession();; context.getBean("service");.bar();; } 其实这种可以称为OpenSessionInClient,不管View还是Test Code的Client,只要在同一个JVM里面,就可以用OpenSession的方式处理掉。如果Hibernate的实体对象丢到另外一个JVM中,还想拥有LazyLoad便捷性,就麻烦多了,Robbin有何好的方法? |
|
返回顶楼 | |
发表时间:2005-07-15
Spring的AbstractTransactionalDataSourceSpringContextTests里的实现也可以参考。
|
|
返回顶楼 | |
发表时间:2005-07-15
引用 其实这种可以称为OpenSessionInClient,不管View还是Test Code的Client,只要在同一个JVM里面,就可以用OpenSession的方式处理掉。如果Hibernate的实体对象丢到另外一个JVM 中,还想拥有LazyLoad便捷性,就麻烦多了,Robbin有何好的方法?
记得这个问题我们和jackz前面曾经讨论过,你还尝试使用Hibernate3某新特性去解决这类问题。我的答案是目前没有好的解决办法。 Hibernate能对实体对象lazy loading的一个前提是对实体对象动态增强,使用了该实体对象的Proxy,而这个Proxy代理了实体对象的getter/setter的方法,在proxy自己的getter/setter方法里面增加数据库查询操作(我没有看过这部分源代码,只是推测)。 根据这个原理,要想实现跨JVM的lazy loading,即分布式调用,那么实体对象的远程Proxy类,必须具备可以定位服务器地址,发送远程方法调用的能力,这将要求如下条件: 1、持有该实体类的客户端必须有Hibernate的Session环境,实体类必须在一个Session的scope范围之内load,来支持生成实体类的远程Proxy。 2、服务器端的实体类的Proxy类必须具备远程方法调用的能力,即可以作为服务器的一项服务被发布出来,可以被客户端查找并且调用。 其实传统的CMP就是这样的模型,满足上面所述的条件,可以支持跨JVM的lazy loading。所以我在观望EJB3 EntityBean,看看它出来以后,是否还具备这样的能力。 不过这种分布式lazy loading的需求往往客户端和服务器端环境是不一样的,很可能是服务器端是Java,而客户端环境是AJAX,或者Flash ,这样的话,EJB3也无用武之地了。 在RIA模型中,其实现在已经遇到这种麻烦了。当前的RIA模型,以Flash AMF为例,服务器端有一个Flash Gateway,其实就是一个Servlet,这个Servlet接受Flash发送过来的消息(Flash使用AMF协议,类似Hessian/Burlap,但是协议更完整),然后根据消息,去调用相应的服务器端组件(例如现在可以直接按照bean的id去调用相应的Spring Bean),然后把结果封装为AMF消息返回。 例如一个Hibernate实体对象通过AMF调用序列化到Flash里面了,即成为Flash里面的一个ActionScript Object。这个AS的Object是无法lazy loading的。在默认情况下,AMF Gateway在把实体对象序列化到客户端的时候,其序列化过程会遍历整个对象图,将整张对象图全部序列化到客户端。 针对这个问题,我当时的做法就是修改OpenAMF的序列化源代码,当遇到one-to-many的关联,即打断关联关系,不序列化关联集合,而many-to-one和one-to-one关联则序列化关联类。 客户端的Flash AS Object如果需要关联集合,则需要使用这个对象作为消息,再次显式发送AMF调用,获取该实体对象的关联集合。 这种方式下,由于打断one-to-many关联,所以不再支持inverse="false",即父对象维护关联,所以要求所有的one-to-many都改为双向关联,关联关系由子对象一方维护。 这样你就可以在Flash里面,先通过AMF调用,获得一个父对象,然后再调用一次获得这个对象的集合属性,然后可以进行各种关联关系的维护操作,最后AMF调用子对象的update操作,即可。 这是我目前的折衷的做法,我觉得还可以接受,这种方式下,仅仅要求你在客户端多发送一次显式查询来loading集合属性,其它方面和在服务器端直接操作没有多大区别。 |
|
返回顶楼 | |
发表时间:2005-08-18
如果session的关闭由域模型来控制的话...lazyload的似乎更好理解了
因为client想使用的时候域模型(自己带有lazy load),并不关心manager是什么,所以session放在manager层管理,就会出现lazyload的尴尬问题..如果由域模型自己管理,又会出现多session问题(manager被多次调用.返回多个域模型) 个人拙见... |
|
返回顶楼 | |