Spring Service #Transactional doesn't rollback transaction Mybatis SqlSession - spring

The goal is to rollback all/any transactions in case of failure. But this doesn't work as expected.
We use Spring MVC + JMS + Service + Mybatis. In the logs, the JMS is set to rollback, but the row is inserted and not rollback. Would like to know what I'm missing or doing wrong?
The #Transactional tag was added recently. So not sure if it works as expected.
Code:
Service Class:
#Transactional(value = "transactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class DataExchangeLogic implements DataExchangeService {
private DataExchDao dataExchDao;
...
#Override
public void save(DataExch dataExch) throws ValidationException {
if (dataExch.getId() != null && dataExch.getId() > 0) {
this.dataExchDao.update(dataExch);
} else {
//LOGGER.debug("in insert::");
this.dataExchDao.create(dataExch);
//Empty exception throw to test rollback
throw new RuntimeException();
}
}
}
DAO:
public interface DataExchDaoMybatis
extends NotificationDao {
void create(DataExch dataExch);
}
Spring Context
<bean id="dataExchLogic" class="com.abc.service.logic.DataExchLogic">
<property name="dataExchDao" ref="dataExchDao" />
</bean>
EAR/WAR project Spring Context
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager" />
<tx:annotation-driven transaction-manager="transactionManager" />
Logs:
[31mWARN [0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Setup of JMS message listener invoker failed for destination 'queue://REQUEST?priority=1&timeToLive=500000' - trying to recover. Cause: Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:720)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:240)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1142)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1134)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1031)
at java.lang.Thread.run(Thread.java:745)
[34mINFO [0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Successfully refreshed JMS Connection
[39mDEBUG[0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Received message of type [class com.ibm.ws.sib.api.jms.impl.JmsTextMessageImpl] from consumer [com.ibm.ws.sib.api.jms.impl.JmsQueueReceiverImpl#6ca01c74] of transactional session [com.ibm.ws.sib.api.jms.impl.JmsQueueSessionImpl#3ac3b63]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
JDBC Connection [com.ibm.ws.rsadapter.jdbc.WSJdbcConnection#19b89f0c] will be managed by Spring
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # ==> Preparing: SELECT ID.NEXTVAL FROM DUAL
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # ==> Parameters:
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # <== Total: 1
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # ==> Preparing: INSERT INTO TABLE ( COL1, COL2, COL N) VALUES ( ?, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?)
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # ==> Parameters: 468(Integer), SYSTEM(String), 2017-03-01 00:00:00.0(Timestamp), 2017-03-16 00:00:00.0(Timestamp), true(Boolean), test 112(String), ALL(String)
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # <== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
EDIT 1:
Controller code:
#ResourceMapping(value = "addNewDEURL")
public void addNewDE(#ModelAttribute(value = "dataObject") final DataExch dataExch,
final BindingResult bindingResult, final ResourceResponse response) {
if (!bindingResult.hasErrors()) {
try {
dataExchangeService.save(dataExch);
} catch (final ValidationException e) {
logger.error("A validation exception occurred.", e);
}
} else {
logger.error(bindingResult.getAllErrors().get(0)
.getDefaultMessage());
}
}
DAO changed:
public class DataExchDaoMybatis extends BaseDaoImpl implements DataExchDao {
public void create(DataExch dataExch) {
doSimpleInsert("insertDE", dataExch);
}
}
BaseDaoImpl:
public void doSimpleInsert(String queryId, Object o) {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert(queryId, o);
}

Please put transactionManager configuration and tx:annotation-driven into root spring context
Rule: Root context can see all the beans which Spring created. Child context(any Web Context) can see only its own beans.
In this particular case tx:annotation-driven looks for beans with #Transactional annotation in Web context. It cannot find any because you defined dataExchLogic in root context. That's why you didn't have any transactional behavior.
#EnableTransactionManagement and only looks for #Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for #Transactional beans in your controllers, and not your services. See Section 21.2, “The DispatcherServlet” for more information.
Solution implies to move tx:annotation-driven to the root context because Root Context can find any bean defined either in root or in any web context.

Quoting from spring documentation:
You can place the #Transactional annotation before an interface
definition, a method on an interface, a class definition, or a public
method on a class. However, the mere presence of the #Transactional
annotation is not enough to activate the transactional behavior. The
#Transactional annotation is simply metadata that can be consumed by
some runtime infrastructure that is #Transactional-aware and that can
use the metadata to configure the appropriate beans with transactional
behavior. In the preceding example, the
element switches on the transactional behavior.
Which means,
void create(DataExch dataExch);
should be
public void create(DataExch dataExch);
#Transactional annotation behavior is not exhibited if it is not applied on a public method.
EDIT:
Since my answer was downvoted, to support my answer and to shed some light on the transactional behavior when a Transactional annotated method calls a method without annotation, take a look at this:
#Transactional method calling another method without #Transactional anotation? specifically the answer by Arun P Johny

I think of two possibilities.
1) Your DAO class is starting a new transaction.
2) Your DAO class is not participating in the transaction.
I dont see any other reason why the data should be updated to the database. Can you add below property to log4j to see how many transactions are being started.
log4j.logger.org.springframework.transaction.interceptor = trace
Also syosut the below transaction status in Service and DAO method to see if the transaction is active.
TransactionSynchronizationManager.isActualTransactionActive()
Let us know what happens.

Related

Spring Retry with Transactional Annotation

Is the below code the correct way to use Spring Retry with Transactional?
Or do I need to take care of anything extra ? I am using latest Spring Boot version
Is retry tried after the failed transaction is closed ?
#Repository
public class MyRepository {
#Retryable( value = CustomRetryAbleException.class, maxAttempts = 2, backoff = #Backoff(delay = 30000))
#Transactional
Employee updateAndGetEmployee(String date) throw CustomRetryAbleException;
{
try{
jdbcTemplate.exceute( ....) ; //Call Stored Proc
}
catch(CustomRetryAbleException c )
{
throw CustomRetryAbleException (" Retry this Exception " );
}
}
'This is the way.'
Do not forget to put the #EnableRetry annotation on either your config-class (annotated with #Configuration) or your application-class (annotated with #SpringBootApplication).
Read this for more information.
You can just log something and intentionally make it fail to see if it gets logged again after the delay.

Why is Ebean ORM throwing java.sql.SQLException: IJ031021: You cannot rollback during a managed transaction

I have a Jax-RS Rest service that uses Ebean to query the database. On any query I make this exception is thrown.
For example.
User currentUser = new QUser().where().id.eq(currentUserID)).findUnique();
Logs
ERROR [io.ebeaninternal.server.transaction.JdbcTransaction] (default task-10) Error when ending a query only transaction via ROLLBACK: java.sql.SQLException: IJ031021: You cannot rollback during a managed transaction
Now the query returns the appropriate user and doesn't interfere with the Jax-RS.
But I can't ignore the large code-smell
And the huge log that is created because it gets thrown on every query.
My Configuration
ServerConfig config = new ServerConfig();
config.setDataSource(ds);
config.setName("db");
config.setAutoCommitMode(false);
config.setDatabasePlatform(new PostgresPlatform());
config.setRegister(true);
config.setDefaultServer(true);
config.setTransactionRollbackOnChecked(true);
config.addPackage(User.class.getPackage().getName());
EbeanServer es = EbeanServerFactory.create(config);
When using ebean inside Java EE you need to configure the EbeanServer before it is used. A typical place to do it is in a #PostConstruct method in a #Startup #Singleton bean-managed transaction ejb. And you need to configure it to use the JTA transaction manager so it doesn't try to begin/commit the transactions on its own.
#Singleton
#Startup
#TransactionManagement(TransactionManagementType.BEAN)
public class AtStartup {
#Resource(mappedName = "java:jboss/datasources/EbeanTestDS")
private DataSource ds;
#SneakyThrows
#PostConstruct
public void startup() {
new MigrationRunner(new MigrationConfig()).run(ds); // begin/commits transaction for the migration...
ServerConfig config = new ServerConfig();
config.setDataSource(ds);
config.addPackage(Customer.class.getPackage().getName());
config.setUseJtaTransactionManager(true); // This is important !
config.setAutoCommitMode(false);
EbeanServerFactory.create(config);
}

Correct use of Hazelcast Transactional Map in an Spring Boot app

I am working on a proof of concept of Hazelcast Transactional Map. To accomplish this I am writing an Spring Boot app and using Atomikos as my JTA/XA implementation.
This app must update a transactional map and also update a database table by inserting a new row all within the same transaction.
I am using JPA / SpringData / Hibernate to work with the database.
So the app have a component (a JAVA class annotated with #Component) that have a method called agregar() (add in spanish). This method is annotated with #Transactional (org.springframework.transaction.annotation.Transactional)
The method must performe two task as a unit: first must update a TransactionalMap retrieved from Hazelcast instance and, second, must update a database table using a repository extended from JpaRepository (org.springframework.data.jpa.repository.JpaRepository)
This is the code I have written:
#Transactional
public void agregar() throws NotSupportedException, SystemException, IllegalStateException, RollbackException, SecurityException, HeuristicMixedException, HeuristicRollbackException, SQLException {
logger.info("AGRENADO AL MAPA ...");
HazelcastXAResource xaResource = hazelcastInstance.getXAResource();
UserTransactionManager tm = new UserTransactionManager();
tm.begin();
Transaction transaction = tm.getTransaction();
transaction.enlistResource(xaResource);
TransactionContext context = xaResource.getTransactionContext();
TransactionalMap<TaskKey, TaskQueue> mapTareasDiferidas = context.getMap("TAREAS-DIFERIDAS");
TaskKey taskKey = new TaskKey(1L);
TaskQueue taskQueue = mapTareasDiferidas.get(taskKey);
Integer numero = 4;
Task<Integer> taskFactorial = new TaskImplFactorial(numero);
taskQueue = new TaskQueue();
taskQueue.getQueue().add(taskFactorial);
mapTareasDiferidas.put(taskKey, taskQueue);
transaction.delistResource(xaResource, XAResource.TMSUCCESS);
tm.commit();
logger.info("AGRENADO A LA TABLA ...");
PaisEntity paisEntity = new PaisEntity(100, "ARGENTINA", 10);
paisRepository.save(paisEntity);
}
This code is working: if one of the tasks throw an exception then both are rolled back.
My questions are:
Is this code actually correct?
Why #Transactional is not taking care of commiting the changes in the map and I must explicitylly do it on my own?
The complete code of the project is available en Github: https://github.com/diegocairone/hazelcast-maps-poc
Thanks in advance
Finally i realized that i must inject the 'UserTransactionManager' object and take the transaction from it.
Also is necessary to use a JTA/XA implementation. I have chosen Atomikos and XA transactions must be enable in MS SQL Server.
The working example is available at Github https://github.com/diegocairone/hazelcast-maps-poc on branch atomikos-datasource-mssql
Starting with Hazelcast 3.7, you can get rid of the boilerplate code to begin, commit or rollback transactions by using HazelcastTransactionManager which is a PlatformTransactionManager implementation to be used with Spring Transaction API.
You can find example here.
Also, Hazelcast can participate in XA transaction with Atomikos. Here's a doc
Thank you
I have updated to Hazelcast 3.7.5 and added the following code to HazelcastConfig class.
#Configuration
public class HazelcastConfig {
...
#Bean
public HazelcastInstance getHazelcastInstance() {
....
}
#Bean
public HazelcastTransactionManager getTransactionManager() {
HazelcastTransactionManager transactionManager = new HazelcastTransactionManager(getHazelcastInstance());
return transactionManager;
}
#Bean
public ManagedTransactionalTaskContext getTransactionalContext() {
ManagedTransactionalTaskContext transactionalContext = new ManagedTransactionalTaskContext(getTransactionManager());
return transactionalContext;
}
When I run the app I get this exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'transactionManager' available: No matching
PlatformTransactionManager bean found for qualifier
'transactionManager' - neither qualifier match nor bean name match!
The code is available at Github on a new branch: atomikos-datasource-mssql-hz37
Thanks in advance

Some transaction propagations not working with Spring/Hibernate 4

I'm in the process of upgrading an application to Hibernate 4.2 from 3.3. We're also using Spring 3.1.3 (which we can't/won't update at this time).
Some of my unit tests are now failing with
org.hibernate.HibernateException: No Session found for current thread
in the SpringSessionContext. This is not an issue with the <tx:annotation-driven /> being defined in the wrong context, or a case of missing CGLIB libraries. Most of the tests do work, which means that in most cases, the transaction proxying is working.
The cases where it is now failing seem to be around the use of NOT_SUPPORTED, NEVER, and SUPPORTED propagation types. For whatever reason the SpringSessionContext doesn't create a session in these cases.
Our use cases sometimes require that transactional boundaries don't strictly line up with method boundaries, and that sessions sometimes outlive transactions. In the Spring 3/Hibernate 3 case, the session context was bound to a thread local, and a call to SessionFactory.getCurrentSession() would return a session instance even if a transaction was not started. This is the behavior that I am looking to still have in the Hibernate 4 case.
Does anyone know a workaround for this? It's tough to align Session boundaries with a conversation instead of a transaction if Spring refuses to create a session without a valid transaction. A Session and its persistence context shouldn't be tied to an open transaction.
Worked around this issue by implementing a CurrentSessionContext that is a wrapper around a SpringSessionContext and borrowing some of the code changes that made it into Spring Framwork 4+:
public class ClassLoaderSpringSessionContext implements CurrentSessionContext {
private final SessionFactoryImplementor sessionFactory;
private final SpringSessionContext sessionContext;
public ClassLoaderSpringSessionContext(final SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory; // This is actually some class loading logic that isn't important to this transaction problem.
this.sessionContext = new SpringSessionContext(this.sessionFactory);
}
#Override
public Session currentSession() throws HibernateException {
try {
return sessionContext.currentSession();
} catch (HibernateException e) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager
.registerSynchronization(new SpringSessionSynchronization(sessionHolder,
this.sessionFactory));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
return session;
} else {
throw new HibernateException(
"Could not obtain transaction-synchronized Session for current thread");
}
}
}
}
SpringSessionSynchronization was a package private class in Spring 4, so I also had to pull a version of that as a private inner class to ClassLoaderSpringSessionContext.
Hope this helps someone else.

JMS doesn't rollback XA transaction (or doesn't participate in one)

I'm relatively new to XA transactions. I've been struggling a few days to make a simple XA transaction work to no avail.
First, I tried to use two different databases. I set up 2 XA datasources and had succeeded in rolling back the first database operation when the second fails. So far, so good. But then I tried to replace second datasource with JMS connectionFactory and cannot reproduce the same behavior.
Here's the relevant code:
Database logic:
#Stateless
public class FirstDB implements FirstDBLocal {
#PersistenceContext(unitName = "xaunit")
private EntityManager em;
public void doSomething() {
SomeEntity someEntity = em.find(SomeEntity.class, 1234L);
someEntity.setSomeFlag(false);
}
}
JMS code:
#Stateless
public class SecondJMS implements SecondJMSLocal {
#Resource(mappedName = "java:/JmsXA")
private ConnectionFactory connFactory;
#Resource(mappedName = "queue/Some.Queue")
private Queue q;
#Override
#TransactionAttribute(TransactionAttributeType.MANDATORY)
public void sendMsg() {
Session session = null;
Connection conn = null;
MessageProducer producer = null;
try {
conn = connFactory.createConnection("guest", "guest");
session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(q);
// Not sure if I need this, but I found it in the sample code
conn.start();
TextMessage tm = session.createTextMessage(new Date().toString());
producer.send(tm);
throw new RuntimeException("Fake exception");
} catch (JMSException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
// close all resources
}
}
}
The glue code:
#Stateless
public class TestDBandJMS implements TestDBandJMSLocal {
#EJB
private FirstDBLocal firstDBLocal;
#EJB
private SecondJMSLocal secondJMSLocal;
public void doStuff() {
firstDBLocal.doSomething();
secondJMSLocal.sendMsg();
}
}
XA Connection Factory configuration (everything is JBoss default, except for commented out security settings):
<tx-connection-factory>
<jndi-name>JmsXA</jndi-name>
<xa-transaction/>
<rar-name>jms-ra.rar</rar-name>
<connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
<config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
<config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/DefaultJMSProvider</config-property>
<max-pool-size>20</max-pool-size>
<!-- <security-domain-and-application>JmsXARealm</security-domain-and-application> -->
<depends>jboss.messaging:service=ServerPeer</depends>
</tx-connection-factory>
I also have very simple MDB which just prints out received message to console (not going to post the code, since it's trivial).
The problem is, when the exception is thrown in JMS code, the message is still received by MDB and SomeEntity is successfully updated in the database code (whereas I expect it to rollback).
Here is the JMS log. One fishy thing that I see there is this:
received ONE_PHASE_COMMIT request
Like I said, I'm not too familiar with XA yet, but I expect to see here TWO_PHASE_COMMIT, because there should be 2 resources which participate in the active transaction.
Any help would be much appreciated.
UPDATE
It worked eventually, after I tried #djmorton's suggestion.
One other important thing to keep in mind when working with JBoss 5.1 is that the lookup name for XA JMS ConnectionFactory is "java:/JmsXA". I tried the same with
#Resource(mappedName = "XAConnectionFactory")
private ConnectionFactory connFactory;
and it didn't work.
You are catching your RuntimeException after throwing it in your sendMsg() method. The Exception will not trigger a transaction rollback unless it is thrown up the stack. When using Container managed transactions, the container adds interceptors to the method calls to setup the transactions and handle rollbacks when unchecked exceptions are thrown. If the exception isn't thrown out of the method the interceptor doesn't know it needs to role the transaction back.
Edit 1:
Note that only a RuntimeException or a subclass of RuntimeException being thrown will cause the transaction to rollback. A checked exception (One that extends Exception rather than RuntimeException) will not cause a rollback unless it is annotated with #ApplicationException(rollback=true).
The other alternative is to inject an EJBContext object, and call .setRollbackOnly() to force the transaction to rollback when the method goes out of scope:
#Stateless
public class SomeEjb {
#Resource
private EJBContext context;
#TransactionAttribute(TransactionAttributeType.MANDATORY)
public void rollMeBack() {
context.setRollbackOnly();
}
}

Resources