spring hibernate 事务管理 OpenSessionInViewFilter

本文探讨了Spring Hibernate事务管理中OpenSessionInViewFilter的问题,分析了配置、doFilterInternal()方法的关键部分,以及Session获取和flushMode设置的影响。通过调整filter配置和设置flushMode为auto,可以解决事务管理中的问题。

spring 配置文件的问题。关键是细心。

HibernateDaoSupport的子类在保存实体时抛出InvalidDataAccessApiUsageException异常,异常堆栈如下:

org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
at org.springframework.orm.hibernate3.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1090)
at org.springframework.orm.hibernate3.HibernateTemplate$12.doInHibernate(HibernateTemplate.java:629)
at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:367)
at org.springframework.orm.hibernate3.HibernateTemplate.save(HibernateTemplate.java:627)

在网上搜了一下,其中大多数文章又是提OpenSessionInViewFilter又是提OpenSessionInViewInterceptor的,大多云山雾罩、不知所云。

其实这个异常的提示还是很明确的:在只读模式下(FlushMode.NEVER/MANUAL)写操作不被允许:把你的Session改成FlushMode.COMMIT/AUTO或者清除事务定义中的readOnly标记。

首先看一下我Spring的配置文件,为了减少篇幅,仅将与事务有关的一部分贴在下面:

Xml代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">  
  3. <beans>  
  4.      <!-- Transaction template for Managers, from:   
  5.          http://blog.exis.com/colin/archives/2004/07/31/concise-transaction-definitions-spring-11/ -->  
  6.     <bean id="txProxyTemplate" abstract="true"   
  7.         class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
  8.         <property name="transactionManager">  
  9.             <!--org.springframework.orm.hibernate3.HibernateTransactionManager的Bean实例-->  
  10.             <ref bean="gkgltransactionManager" />  
  11.         </property>  
  12.         <property name="transactionAttributes">  
  13.             <props>  
  14.                 <prop key="save*">PROPAGATION_REQUIRED</prop>  
  15.                 <prop key="remove*">PROPAGATION_REQUIRED</prop>  
  16.                 <prop key="do*">PROPAGATION_SUPPORTS</prop>   
  17.                 <prop key="auto*">PROPAGATION_SUPPORTS</prop>  
  18.                 <!--对于其它方法要求事务并且是只读的-->  
  19.                 <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>  
  20.             </props>  
  21.         </property>  
  22.     </bean>  
  23.     <!-- Generic manager that can be used to do basic CRUD operations on any objects -->  
  24.     <bean id="manager" parent="txProxyTemplate">  
  25.         <property name="target">  
  26.             <bean class="com.neuqsoft.base.service.impl.BaseManager">  
  27.                 <property name="dao">  
  28.                     <ref bean="gkgldao" />  
  29.                 </property>  
  30.             </bean>  
  31.         </property>  
  32.     </bean>  
  33. </beans>  

对于清除readOnly标记是很简单的,只需把上述配置文件中txProxyTemplate Bean定义中的<prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>中的readOnly及其前面的逗号去掉即可。

接下来我们讨论把Session改成FlushMode.COMMIT/AUTO,下面是HibernateTemplate中checkWriteOperationAllowed方法的源码:

Java代码
  1. protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {   
  2.         if (isCheckWriteOperations() && getFlushMode() != FLUSH_EAGER &&   
  3.                  session.getFlushMode().lessThan(FlushMode.COMMIT)) {   
  4.             throw new InvalidDataAccessApiUsageException(   
  5.                     "Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): "+   
  6.                     "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");   
  7.          }   
  8.      }  
protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
  if (isCheckWriteOperations() && getFlushMode() != FLUSH_EAGER &&
    session.getFlushMode().lessThan(FlushMode.COMMIT)) {
   throw new InvalidDataAccessApiUsageException(
     "Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): "+
     "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.");
  }
 }

通过阅读这段代码我们不难看出,在Java代码中调用HibernateTemplate的save或者saveOrUpdate等涉及到写操作的方法之前需要把Session的刷新模式设置为FlushMode.COMMIT或更高的级别,或者把HibernateTemplate的刷新模式设置为FLUSH_EAGER,由于我们的Dao继承自HibernateDaoSupport,所以设置Session刷新模式的语句如下:getSession().setFlushMode(FlushMode.COMMIT);

而设置HibernateTemplate刷新模式的语句如下:

Java代码
  1. HibernateTemplate tmp=getHibernateTemplate();      
  2. tmp.setFlushMode(HibernateTemplate.FLUSH_EAGER);    
HibernateTemplate tmp=getHibernateTemplate();   
tmp.setFlushMode(HibernateTemplate.FLUSH_EAGER);

只要在调用HibernateTemplate涉及到写操作的方法之前正确设置了HibernateTemplate或者Session的刷新模式,则上述异常不会再抛出

 

 

 

 

 

 

 

 

 

 

 

第二种说法:

 

 

org. springframework. dao.InvalidDataAccessApiUsageException:
Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.


实际上这个问题不是第一次出现了 只是一直没有时间来研究它。今天得闲随即打开了OpenSessionInViewFilter这个文件。 首先先看一下我的web.xml配置

Java代码 复制代码
  1. <!-- openSessionInView -->   
  2.     <filter>   
  3.         <filter-name>openSessionInView</filter-name>   
  4.         <filter-class>   
  5.             org.springframework.orm.hibernate3.support.OpenSessionInViewFilter   
  6.         </filter-class>   
  7.     </filter>   
  8.     <filter-mapping>   
  9.         <filter-name>openSessionInView</filter-name>   
  10.         <url-pattern>/*</url-pattern>   
  11.     </filter-mapping>   
  12.        
  13.     <!--==================== rewriteFilter ====================-->   
  14.   
  15.     <!--==================== Spring ApplicationContext ====================-->   
  16.     <listener>   
  17.         <listener-class>   
  18.             org.springframework.web.context.ContextLoaderListener   
  19.         </listener-class>   
  20.     </listener>   
  21.     <context-param>   
  22.         <param-name>contextConfigLocation</param-name>   
  23.         <param-value>   
  24.             classpath*:com/redgateonline/showradio/resource/spring/applicationContext*.xml   
  25.         </param-value>   
  26.     </context-param>   
  27.   
  28.     <!-- ==================== Webwork =================== -->  
<!-- openSessionInView -->
	<filter>
		<filter-name>openSessionInView</filter-name>
		<filter-class>
			org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
		</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>openSessionInView</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!--==================== rewriteFilter ====================-->

	<!--==================== Spring ApplicationContext ====================-->
	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath*:com/redgateonline/showradio/resource/spring/applicationContext*.xml
		</param-value>
	</context-param>

	<!-- ==================== Webwork =================== -->


之前一直怀疑是filter配置的顺序问题 ,有几次修改了配置文件后问题就解决了。
先来看看OpenSessionInViewFilter中的doFilterInternal()方法,这里只给出关键部分,

Java代码 复制代码
  1. //从servletContext中取sessionFactory   
  2. SessionFactory sessionFactory = lookupSessionFactory(request);   
  3.         boolean participate = false;   
  4.   
  5.         if (isSingleSession()) {   
  6.             // single session mode   
  7.             if (TransactionSynchronizationManager.hasResource(sessionFactory)) {   
  8.                 // Do not modify the Session: just set the participate flag.   
  9.                 participate = true;   
  10.             }   
  11.             else {   
  12.                 logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");   
  13.                    
  14. Session session = getSession(sessionFactory); //通过sessionFactory获取session,这个session被绑定到当前线程中 在整个request期间使用   
  15.                 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));   
  16.             }   
  17.         }   
  18.         else {   
  19.             // deferred close mode   
  20.             if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {   
  21.                 // Do not modify deferred close: just set the participate flag.   
  22.                 participate = true;   
  23.             }   
  24.             else {   
  25.                 SessionFactoryUtils.initDeferredClose(sessionFactory);   
  26.             }   
  27.         }  
//从servletContext中取sessionFactory
SessionFactory sessionFactory = lookupSessionFactory(request);
		boolean participate = false;

		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 OpenSessionInViewFilter");
				
Session session = getSession(sessionFactory); //通过sessionFactory获取session,这个session被绑定到当前线程中 在整个request期间使用
				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 session = getSession(sessionFactory);中的getSession(sessionFactory)是怎么实现的

Java代码 复制代码
  1. protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {   
  2.         Session session = SessionFactoryUtils.getSession(sessionFactory, true);   
  3.         FlushMode flushMode = getFlushMode();   
  4.         if (flushMode != null) {   
  5.             session.setFlushMode(flushMode);   
  6.         }   
  7.         return session;   
  8.     }  
protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
		Session session = SessionFactoryUtils.getSession(sessionFactory, true);
		FlushMode flushMode = getFlushMode();
		if (flushMode != null) {
			session.setFlushMode(flushMode);
		}
		return session;
	}


OpenSessionInViewFilter有一个变量flushMode,可通过getter setter为其赋值,而其默认值为FlushMode.NEVER,这儿的NEVER值已经被deprecate了。
原来是这儿在捣鬼,只要写了子类覆盖setFlushMode(FlushMode.auto);就ok了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值