正在使用的版本:Spring 4.1.6.RELEASE,Hibernate 4.3.10.Final,Atomikos 3.9.26

我们正在将主Web应用程序升级到Hibernate 4.我们主要使用 HibernateTemplateJdbcTemplate 来访问多个数据库(DB2和Oracle),使用Atomikos作为JTA-TransactionManager .

The problem: 虽然在单个事务中仅使用 HibernateTemplate 或仅使用 JdbcTemplate 可正常工作,但在某个事务中一起使用 JdbcTemplateHibernateTemplate 会导致 StaleStateException s .

以下是发生问题的示例 - 代码包含在带有 PROPAGATION_REQUIREDTransactionalProxyFactoryBean 中:

public class MyServiceImpl extends HibernateDaoSupport implements MyService {

  private static final Log log = LogFactory.getLog(MyServiceImpl.class);

  private JdbcTemplate jdbcTemplate;

  @Override
  public void execute() {
    // save new entity instance with HibernateTemplate
    MyEntity e = new MyEntity();
    e.setMyProperty("first value");
    getHibernateTemplate().save(e);

    // use JdbcTemplate to access DB
    String sql = "select * from my_table";
    getJdbcTemplate().query(sql, new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
             // process rows
            }
         });

     // update entity instance with HibernateTemplate
     e.setMyProperty("second value");
     getHibernateTemplate().saveOrUpdate(e);

     // make sure the flush occurs immediately. This is needed in to demonstrate the problem. (Otherwise the property UPDATE would be cached and issued on commit, just after Spring closed the connection used for the JdbcTemplate and the problem would not show)
     getHibernateTemplate().flush();
  }

  public JdbcTemplate getJdbcTemplate() {
    return jdbcTemplate;
  }

  public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }
}

Our conclusions: 该异常基本上是由 HibernateTemplateJdbcTemplate 获取并释放数据库连接的不同方式引起的 .

HibernateTemplate 直接委托给使用连接释放模式AFTER_STATEMENT的Hibernate(如果提供了 JtaTransactionManager ,则由Spring设置) . 这会导致Hibernate从Atomikos连接池获取连接,执行SQL并关闭其不关闭物理连接但将其返回到连接池的连接 .

JdbcTemplate 使用Spring的 DataSourceUtils.getConnection(...) 从Atomikos连接池获取连接,执行SQL并调用 DataSourceUtils.releaseConnection(...) ,它本身不调用 Connection.close() . Spring在 DataSourceUtils.releaseConnection(...) 中没有关闭连接(因此没有返回到连接池),而是绑定到线程以便在 DataSourceUtils.getConnection(...) 中重用 .

所以看起来好像在JTA上下文中,Spring教Hibernate使用连接释放模式AFTER_STATEMENT(这也是Hibernate为JTA推荐的),但它的行为完全不同于 DataSourceUtils .

详细地说,我们跟踪原因如下:

  • 抛出了 StaleStateException ,因为在实体上设置"second value"的UPDATE-Statement不会影响数据库中的任何行 .

  • 这是因为UPDATE语句发生在INSERT语句之外的另一个连接上 .

  • 这是因为INSERT-Statement使用的原始连接仍被连接池认为正在使用 .

  • 这是因为在用于 JdbcTemplate 之后,第一个连接上从不调用close() .

  • 这是因为完成时 JdbcTemplate 调用的 DataSourceUtils.releaseConnection(...) 在JTA-Transaction-Context中不调用 Connection.close() .

我们在尝试和失败的事情:

  • 使Hibernate使用AFTER_TRANSACTION或ON_CLOSE作为连接释放模式 - 由Spring将其阻止为 SpringJtaSessionContext ,其中AFTER_STATEMENT是硬编码的 .

  • 配置Spring在连接释放时关闭数据库连接 .

我们做错了什么?
我们忘记的任何配置?
它是一个Spring / Hibernate问题还是Atomikos连接池在再次使连接可用之前不等待对 Connection.close() 的调用会表现不同?

非常感谢你的帮助!

Spring context for Hibernate and JTA configuration:

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">

    <property name="dataSource">
        <ref bean="dataSource" />
    </property>

    <property name="jtaTransactionManager" ref="transactionManager" />

    <property name="hibernateProperties">
        <props>
            <!-- Stripped down configuration for the toy project to reproduce the problem -->
            <prop key="hibernate.cache.use_second_level_cache">false</prop>
            <prop key="hibernate.dialect">com.company.DB2Dialect</prop>

            <!-- hibernate.transaction.factory_class and hibernate.transaction.jta.platform are implicitly set by setting the jtaTransactionManager property -->

            <!-- Properties wie normally use in production
            <prop key="hibernate.dialect">com.company.DB2Dialect</prop>
            <prop key="hibernate.cache.use_query_cache">false</prop>
            <prop key="hibernate.cache.use_second_level_cache">true</prop>
            <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
            <prop key="hibernate.order_inserts">true</prop>
            <prop key="hibernate.order_updates">true</prop>
            <prop key="hibernate.generate_statistics">false</prop>
            <prop key="hibernate.use_outer_join">true</prop>
            <prop key="hibernate.jdbc.batch_versioned_data">true</prop>
            <prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
            <prop key="hibernate.jdbc.batch_size">100</prop> -->
        </props>
    </property>

    <property name="mappingLocations">
        <list>
            <value>classpath*:**/*.hbm.xml</value>
        </list>
    </property>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter"
    scope="singleton">
    <property name="targetDataSources">
        <map>
            <entry key="ISOLATION_REPEATABLE_READ" value="java:comp/env/jdbc/wawi_rr" />
            <entry key="ISOLATION_READ_UNCOMMITTED" value="java:comp/env/jdbc/wawi_ru" />
            <entry key="ISOLATION_READ_COMMITTED" value="java:comp/env/jdbc/wawi_rc" />
            <entry key="ISOLATION_SERIALIZABLE" value="java:comp/env/jdbc/wawi_s" />
        </map>
    </property>
    <property name="defaultTargetDataSource" value="java:comp/env/jdbc/wawi" />
</bean>


<bean id="transactionManager"
    class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManagerName">
        <value>java:comp/env/TransactionManager</value>
    </property>
    <property name="allowCustomIsolationLevels">
        <value>true</value>
    </property>
</bean>

Spring context for Service configuration:

<bean id="myService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="target">
        <ref bean="myServiceTarget" />
    </property>
    <property name="transactionManager">
        <ref bean="transactionManager" />
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
        </props>
    </property>
</bean>

<bean id="myServiceTarget" class="org.example.MyServiceImpl">
    <property name="sessionFactory" ref="sessionFactory" />
    <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

<bean id="myMBean" class="org.example.MyMBean">
    <property name="myService" ref="myService" />
</bean>

Stacktrace:

org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
    at org.springframework.orm.hibernate4.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:205)
    at org.springframework.orm.hibernate4.HibernateTemplate.doExecute(HibernateTemplate.java:343)
    at org.springframework.orm.hibernate4.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:308)
    at org.springframework.orm.hibernate4.HibernateTemplate.flush(HibernateTemplate.java:837)
    at org.example.MyServiceImpl.execute(MyServiceImpl.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy13.execute(Unknown Source)
    at org.example.MyMBean.execute(MyMBean.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:75)
    at sun.reflect.GeneratedMethodAccessor31.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:279)
    at javax.management.modelmbean.RequiredModelMBean$4.run(RequiredModelMBean.java:1245)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at javax.management.modelmbean.RequiredModelMBean.invokeMethod(RequiredModelMBean.java:1239)
    at javax.management.modelmbean.RequiredModelMBean.invoke(RequiredModelMBean.java:1077)
    at org.springframework.jmx.export.SpringModelMBean.invoke(SpringModelMBean.java:90)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
    at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487)
    at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97)
    at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328)
    at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420)
    at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
    at sun.rmi.transport.Transport$1.run(Transport.java:177)
    at sun.rmi.transport.Transport$1.run(Transport.java:174)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:556)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:811)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:670)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
    at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:81)
    at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:73)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:63)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3281)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
    at org.springframework.orm.hibernate4.HibernateTemplate$27.doInHibernate(HibernateTemplate.java:840)
    at org.springframework.orm.hibernate4.HibernateTemplate.doExecute(HibernateTemplate.java:340)
    ... 54 more