使用中的版本: Spring 4.1.6.RELEASE,Hibernate 4.3.10.Final,Atomikos 3.9.26
我们正在将主Web应用程序升级到Hibernate 4.我们主要使用HibernateTemplate
和JdbcTemplate
来访问使用Atomikos作为JTA-TransactionManager的多个数据库(DB2和Oracle)。
问题:在单个交易中仅使用HibernateTemplate
或仅使用JdbcTemplate
时工作正常,同时使用JdbcTemplate
和HibernateTemplate
在某些情况下,一个事务会导致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;
}
}
我们的结论:异常主要是由HibernateTemplate
和JdbcTemplate
获取和释放数据库连接的不同方式引起的。
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
。
详细地说,我们跟踪原因如下:
StaleStateException
,因为UPDATE-Statement用于设置"第二个值"在实体上不会影响数据库中的任何行。JdbcTemplate
之后,第一个连接上永远不会调用close()。DataSourceUtils.releaseConnection(...)
在完成时JdbcTemplate
调用{J}不会在JTA-Transaction-Context中调用Connection.close()
。我们在尝试和失败的事情:
SpringJtaSessionContext
阻止,其中AFTER_STATEMENT是硬编码的。我们做错了什么?
我们忘记的任何配置?
它是一个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