Update: I was testing it with Bitronix TM and it rollbacks perfectly, so the issue is in JBoss TM (arjuna) or in my configuration.
Update 2 : It looks like transactions are not global, I've tried different datasources, Bitronix datasource has *allowLocalTransactions* property and after setting it my application throws an exception that something tried to use it in local mode. If I use Bitronix with this datasource it works without any errors. I believe there is something wrong in configs.
Hello.
I have an issue with JTA transactions. I'm using Tomcat 7 + Hibernate 4 + Spring 3 + JBoss TS 4 and JTA transactions.
Suppose there is the following method:
@Transactional(propagation = Propagation.REQUIRED)
public void testMethod() {
insertOfSomeNewEntityInstance();
updateOfAnotherEntity();
}
If this method throws "org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)" during "updateOfAnotherEntity()" method execution or any other runtime exception that might happen during "flush" (Hibernate also shows: HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect).
then the result of insertOfSomeNewEntityInstance() execution is not rolled back.
After debugging this issue I found "doCommit" method in org.springframework.transaction.jta.JtaTransaction Manager
If "txObject.getUserTransaction().commit();" fails with RollbackException then this method throws UnexpectedRollbackException and here is the part of org.springframework.transaction.support.AbstractPl atformTransactionManager processCommit(...) that catches it:
I do not see any rollbacks in triggerAfterCompletion() method and after this method everything else just cleans up resources.
To sum up, spring/jboss just commits the result of insertOfSomeNewEntityInstance(), fails to execute updateOfAnotherEntity() because of concurrent modification error, and does not rollback anything. If I manually throw any runtime or checked exception from updateOfAnotherEntity() it rollbacks correctly, the issue occurs only when Hibernate throws some runtime exception during "flush".
hibernate.cfg
jbossts-properties.xml
Part of applicationContext.xml
I've tried everything, nothing helps :(
Update 2 : It looks like transactions are not global, I've tried different datasources, Bitronix datasource has *allowLocalTransactions* property and after setting it my application throws an exception that something tried to use it in local mode. If I use Bitronix with this datasource it works without any errors. I believe there is something wrong in configs.
Hello.
I have an issue with JTA transactions. I'm using Tomcat 7 + Hibernate 4 + Spring 3 + JBoss TS 4 and JTA transactions.
Suppose there is the following method:
@Transactional(propagation = Propagation.REQUIRED)
public void testMethod() {
insertOfSomeNewEntityInstance();
updateOfAnotherEntity();
}
If this method throws "org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)" during "updateOfAnotherEntity()" method execution or any other runtime exception that might happen during "flush" (Hibernate also shows: HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect).
then the result of insertOfSomeNewEntityInstance() execution is not rolled back.
After debugging this issue I found "doCommit" method in org.springframework.transaction.jta.JtaTransaction Manager
Code:
@Override
protected void doCommit(DefaultTransactionStatus status) {
JtaTransactionObject txObject = (JtaTransactionObject) status.getTransaction();
try {
int jtaStatus = txObject.getUserTransaction().getStatus();
if (jtaStatus == Status.STATUS_NO_TRANSACTION) {
throw new UnexpectedRollbackException("JTA transaction already completed - probably rolled back");
}
if (jtaStatus == Status.STATUS_ROLLEDBACK) {
try {
txObject.getUserTransaction().rollback();
}
catch (IllegalStateException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Rollback failure with transaction already marked as rolled back: " + ex);
}
}
throw new UnexpectedRollbackException("JTA transaction already rolled back (probably due to a timeout)");
}
txObject.getUserTransaction().commit();
}
catch (RollbackException ex) {
throw new UnexpectedRollbackException(
"JTA transaction unexpectedly rolled back (maybe due to a timeout)", ex);
}
catch (HeuristicMixedException ex) {
throw new HeuristicCompletionException(HeuristicCompletionException.STATE_MIXED, ex);
}
catch (HeuristicRollbackException ex) {
throw new HeuristicCompletionException(HeuristicCompletionException.STATE_ROLLED_BACK, ex);
}
catch (IllegalStateException ex) {
throw new TransactionSystemException("Unexpected internal transaction state", ex);
}
catch (SystemException ex) {
throw new TransactionSystemException("JTA failure on commit", ex);
}
}
Code:
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
To sum up, spring/jboss just commits the result of insertOfSomeNewEntityInstance(), fails to execute updateOfAnotherEntity() because of concurrent modification error, and does not rollback anything. If I manually throw any runtime or checked exception from updateOfAnotherEntity() it rollbacks correctly, the issue occurs only when Hibernate throws some runtime exception during "flush".
hibernate.cfg
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect">${dialect}</property>
<property name="max_fetch_depth">1</property>
<property name="hibernate.jdbc.batch_size">25</property>
<property name="show_sql">false</property>
<property name="format_sql">false</property>
<property name="use_sql_comments">false</property>
<property name="hibernate.session_factory_name">TestSessionFactory</property>
<property name="hibernate.session_factory_name_is_jndi">false</property>
<property name="hibernate.current_session_context_class">jta</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
<property name="hibernate.transaction.jta.platform">org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform</property>
<property name="hibernate.id.new_generator_mappings">true</property>
<property name="hibernate.cache.infinispan.cfg">infinispan.xml</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.infinispan.InfinispanRegionFactory</property>
</session-factory>
</hibernate-configuration>
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="CoordinatorEnvironmentBean.commitOnePhase">YES</entry>
<entry key="CoordinatorEnvironmentBean.defaultTimeout">300</entry>
<entry key="ObjectStoreEnvironmentBean.transactionSync">ON</entry>
<entry key="CoreEnvironmentBean.nodeIdentifier">1</entry>
<entry key="JTAEnvironmentBean.xaRecoveryNodes">1</entry>
<entry key="JTAEnvironmentBean.xaResourceOrphanFilterClassNames">
com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter
com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter
</entry>
<entry key="CoreEnvironmentBean.socketProcessIdPort">0</entry>
<entry key="RecoveryEnvironmentBean.recoveryModuleClassNames">
com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule
com.arjuna.ats.internal.txoj.recovery.TORecoveryModule
com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule
</entry>
<entry key="RecoveryEnvironmentBean.expiryScannerClassNames">
com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner
</entry>
<entry key="RecoveryEnvironmentBean.recoveryPort">4712</entry>
<entry key="RecoveryEnvironmentBean.recoveryAddress"></entry>
<entry key="RecoveryEnvironmentBean.transactionStatusManagerPort">0</entry>
<entry key="RecoveryEnvironmentBean.transactionStatusManagerAddress"></entry>
<entry key="RecoveryEnvironmentBean.recoveryListener">YES</entry>
</properties>
Code:
<bean class="com.arjuna.ats.jta.TransactionManager" factory-method="transactionManager" id="arjunaTransactionManager"></bean>
<bean class="com.arjuna.ats.jta.UserTransaction" factory-method="userTransaction" id="arjunaUserTransaction"></bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" >
<property name="transactionManager" ref="arjunaTransactionManager"/>
<property name="userTransaction" ref="arjunaUserTransaction"/>
</bean>
<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.XADataSource" destroy-method="close">
<property name="url" value="${database.url}" />
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="username" value="${database.user}" />
<property name="password" value="${database.password}" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" destroy-method="destroy">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation"><value>classpath:hibernate.cfg.xml</value></property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
</props>
</property>
</bean>
<tx:annotation-driven mode="proxy" proxy-target-class="false" transaction-manager="transactionManager"/>