JTA-Transactions - Spring和Hibernate连接发布之间不匹配?

时间:2015-08-11 16:54:21

标签: spring hibernate jta atomikos

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

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

问题:在单个交易中仅使用HibernateTemplate或仅使用JdbcTemplate时工作正常,同时使用JdbcTemplateHibernateTemplate在某些情况下,一个事务会导致StaleStateException

以下是问题发生的示例 - 代码包含在TransactionalProxyFactoryBean PROPAGATION_REQUIRED中:

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;
  }
}

我们的结论:异常主要是由HibernateTemplateJdbcTemplate获取和释放数据库连接的不同方式引起的。

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

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

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

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

  1. 抛出StaleStateException,因为UPDATE-Statement用于设置"第二个值"在实体上不会影响数据库中的任何行。
  2. 这是因为UPDATE语句发生在INSERT语句之外的另一个连接上。
  3. 这是因为INSERT-Statement使用的原始连接仍被连接池认为正在使用。
  4. 这是因为在用于JdbcTemplate之后,第一个连接上永远不会调用close()。
  5. 这是因为DataSourceUtils.releaseConnection(...)在完成时JdbcTemplate调用{J}不会在JTA-Transaction-Context中调用Connection.close()
  6. 我们在尝试和失败的事情:

    1. 让Hibernate使用AFTER_TRANSACTION或ON_CLOSE作为连接释放模式 - 由Spring将其SpringJtaSessionContext阻止,其中AFTER_STATEMENT是硬编码的。
    2. 配置Spring在连接释放时关闭数据库连接。
    3. 我们做错了什么? 我们忘记的任何配置?
      它是一个Spring / Hibernate问题,还是Atomikos连接池的行为不同是因为在再次使连接可用之前没有等待Connection.close()的调用?

      非常感谢你的帮助!

      Hibernate和JTA配置的Spring上下文:

      <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上下文:

      <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>
      

      堆栈跟踪:

      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
      

0 个答案:

没有答案