Spring和Hibernate突然将事务设置为只读

13

我们有一个运行在JBoss 4.2.3上的应用程序,使用Spring 2.5.2和Hibernate 3.2.6.ga。它在Linux JEE01 2.6.16.60-0.54.5-smp上运行,并使用自己的用户。将数据写入另一台机器上的Oracle 10G数据库。

我们正在使用标准的视图→服务→DAO分层方式。其中每个DAO都带有@Repository注释。

这一切24/7运行良好,但每隔几天,有时一天中出现几次,整个系统会进入一个糟糕的状态,无法再向数据库中写入任何内容。这些堆栈跟踪出现在日志中:

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.

我们扫描了整个系统,发现系统中有一个地方将flushmode临时设置为MANUAL,然后在finally块中将其设置回原始值。这是因为在运行此查询之前,我们不想将状态刷新到数据库中。因此,我们不能轻松更改它。正常的FlushMode设置为AUTO,在几个地方我们将其临时设置为COMMIT,并将其切换回默认模式。

只有重新启动服务器才能恢复系统的正常工作。

问题是:为什么系统将所有事务设置为readonly/manual flush mode?我尝试过谷歌搜索,但没有找到解决方案。

这是我们的Spring和Hibernate配置(只显示相关部分):

     <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="dataSource">
   <ref bean="datasourceName" />
  </property>
  <property name="configLocation">
   <value>classpath:hibernate.cfg.xml</value>
  </property>
 </bean>

 <bean id="hibernateInterceptor"
  class="org.springframework.orm.hibernate3.HibernateInterceptor">
  <property name="sessionFactory" ref="sessionFactory" />
 </bean>

 <tx:annotation-driven transaction-manager="txManager" /> 

 <tx:advice id="txAdvice" transaction-manager="txManager" >
  <!-- the transactional semantics... -->
  <tx:attributes >
   <!-- all methods starting with 'get' are read-only -->
   <tx:method name="approve*" read-only="false"
    propagation="REQUIRED" rollback-for="java.lang.Exception" />
   <tx:method name="update*" read-only="false"   
    propagation="REQUIRED" rollback-for="java.lang.Exception"/>
   <tx:method name="save*" read-only="false"
    propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" read-only="false"
    propagation="REQUIRED" rollback-for="java.lang.Exception" />
   <!-- other methods use the default transaction settings (see below) -->
   <tx:method name="*" read-only="true" propagation="REQUIRED" />
  </tx:attributes>
 </tx:advice>

 <aop:config>
  <aop:pointcut id="serviceMethods"
   expression="execution(* com.myapplication.service.*.*(..))"  />
  <aop:advisor advice-ref="txAdvice" 
   pointcut-ref="serviceMethods" />
 </aop:config>

 <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" >
  <property name="sessionFactory" ref="sessionFactory"  />
 </bean>

-- end of spring config --

-- hibernate configuation --
<hibernate-configuration>
    <session-factory name="">
        <property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>
        <property name="show_sql">false</property>
        <property name="use_outer_join">false</property>
        <property name="hibernate.cache.use_query_cache">true</property>
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
        <property name="hibernate.connection.SetBigStringTryClob">true</property>
        <property name="hibernate.jdbc.batch_size">0</property>
    </session-factory>
    <mapping ----/>
</hibernate-configuration>

这是堆栈跟踪:

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:1137)
at org.springframework.orm.hibernate3.HibernateTemplate$16.doInHibernate(HibernateTemplate.java:701)
at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:374)
at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:699)
at nl.company.myapp.dao.impl.GenericDAOImpl.save(GenericDAOImpl.java:94)
at nl.company.myapp.dao.impl.CallDAOImpl.save(CallDAOImpl.java:266)
at nl.company.myapp.dao.impl.CallDAOImpl.save(CallDAOImpl.java:47)
at nl.company.myapp.service.impl.CallServiceImpl.saveCall(CallServiceImpl.java:98)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:592)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:90)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy142.saveCall(Unknown Source)
at nl.company.myapp.view.bean.call.CallDetailBean.doSave(CallDetailBean.java:319)
at nl.company.myapp.view.bean.EditModeAwareBean.save(EditModeAwareBean.java:151)
at sun.reflect.GeneratedMethodAccessor472.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:592)
at org.apache.el.parser.AstValue.invoke(AstValue.java:131)
at org.apache.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:276)
at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:68)
at org.apache.myfaces.trinidad.component.MethodExpressionMethodBinding.invoke(MethodExpressionMethodBinding.java:46)
at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)
at org.apache.myfaces.trinidad.component.UIXCommand.broadcast(UIXCommand.java:190)
at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:458)
at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:763)
at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:82)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:265)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at nl.company.myapp.view.audit.AuditFilter.doFilter(AuditFilter.java:88)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._invokeDoFilter(TrinidadFilterImpl.java:238)
at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:195)
at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:138)
at org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:265)
at org.acegisecurity.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:107)
at org.acegisecurity.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:72)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.ui.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:124)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.providers.anonymous.AnonymousProcessingFilter.doFilter(AnonymousProcessingFilter.java:125)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:81)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:271)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.ui.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:249)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.util.FilterChainProxy.doFilter(FilterChainProxy.java:149)
at org.acegisecurity.util.FilterToBeanProxy.doFilter(FilterToBeanProxy.java:98)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:182)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432)
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
at org.apache.coyote.ajp.AjpProcessor.process(AjpProcessor.java:437)
at org.apache.coyote.ajp.AjpProtocol$AjpConnectionHandler.process(AjpProtocol.java:366)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
at java.lang.Thread.run(Thread.java:595)

这一切都运作良好。


请发布“InvalidDataAccessApiUsageException”的堆栈跟踪。 - Binil Thomas
我会在我再次获得访问权限时进行。 - aseesing
嗨,Binil,我们将服务器迁移到了Windows机器(出于其他原因),但问题仍然存在。它仍然无法重现。我已经在问题中添加了异常。 - aseesing
7个回答

3
这个异常来自于Spring的HibernateTemplate类中以下的代码:

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.");
    }
}

这个检查的理由在此处解释为:

这是Spring 1.1中引入的新一致性检查。

在FlushMode.NEVER中调用HibernateTemplate的save/update/delete方法,可能是危险的:这意味着你正在:

  • 在Spring管理的只读事务中执行此操作,这将永远不会刷新Hibernate Session,即永远不会刷新您的保存/更新/删除调用。新的检查使您意识到在这种情况下不会持久化更改。

  • 或者在FlushMode.NEVER中使用其他线程绑定的Session,例如OpenSessionInViewFilter。如果您覆盖closeSession以在视图渲染后刷新,请同时覆盖getSession,并将Session设置为FlushMode.AUTO。

我强烈反对后者。如果您正在使用OpenSessionInViewFilter,请将其与中间层事务结合使用,而不是让过滤器在请求完成时刷新。

这些内容是否有所启示?

可能存在代码或Spring ORM中的错误。要禁用此检查,可以调用setCheckWriteOperations(false)


谢谢提供的信息。我会尽快调查代码,并回复您。 - aseesing
代码中的某个错误会导致所有线程和会话都进入“只读”模式吗?因为这正是我们目前看到的情况。 - aseesing
我发现通过openSessionInView HibernateFilter获取的所有会话都被设置为“NEVER”。这似乎是可用默认值中最奇怪的选择。 - Marvo

2
检查您的配置,我在 Spring 上下文部分找到以下内容:
<!-- other methods use the default transaction settings (see below) -->
   <tx:method name="*" read-only="true" propagation="REQUIRED" />

通常,只有一些访问类型是只读的,比如 get/find/query 等。 我建议使用。
<!--default is required transaction-->
<tx:method name="*"/>

另外一件事是,您是否使用opensessioninview模式?可以在opensessioninview中正确设置flush mode。您可以在web.xml中使用过滤器或在应用程序上下文配置中使用spring拦截器。


2
在您的web.xml文件中添加以下内容:
<filter>
    <filter-name>openSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    <init-param>
        <param-name>flushMode</param-name>
        <param-value>AUTO</param-value>
    </init-param>
</filter>

你在我的团队需要答案之前大约四小时发了那个帖子。谢谢! - Marvo

1
我在一个bean中使用了2个“baseTransactionProxy”时遇到了这个问题:
首先:
<bean id="generalDao" parent="baseTransactionProxy">
    <property name="target">
        <bean class="com...GeneralDao" parent="baseDAO" />
    </property>
</bean>

第二:

<bean id="ruleDao" parent="baseTransactionProxy">
    <property name="target">
        <bean class="com...RuleDao" parent="baseDAO">
            <constructor-arg ref="generalDao"></constructor-arg>
        </bean>
    </property>
</bean>

我们已经完成了

class ruleDao{
    generalDao.generalSaveOrUpdateAll(hbms); // OK HERE
    saveOrUpdateAll(otherHbms); //Exception here
}

不确定是否有帮助,但似乎在同一代理调用中混合使用两个不同的“baseTransactionProxy”并不好...


1

这是从记忆中得出的,因此不是非常详细。有两种设置FlushMode的方式。我想一种是Spring的方法,另一种是Hibernate的方法。因为我们只使用了(Spring?)一种方式,而其他时间都使用了Hibernate的方式,这使得Hibernate的内部状态出现了问题。这创造了我的问题。当我们使所有内容一致时,问题消失了。


1

我的猜测是这是Spring为您完成的。我记得这是由Spring中的OpenSessionInViewFilter完成的。你在使用它吗?


0

由于您在实现上使用了执行切入点,并且正在使用通用DAO,因此可能无法处理事务,这只是真正问题的表现。

请确保Spring在该调用上按您的预期代理DAO。您可能需要使用target(beanid)语法为通用DAO启用适当的事务。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接