10

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);
}
Harry
  • 546
  • 6
  • 22
  • 50
  • Did your db support transaction? – Blank Mar 08 '17 at 02:26
  • Oracle is the DB. Thanks – Harry Mar 08 '17 at 03:17
  • 1
    Please make sure that your `@Transactional` service won't be proxyed by other framework. According your information, I guess the `DataExchangeLogic` has been proxyed by JMS framework, which will result in `@Transactional` not working. I suggest you to wrap your service `DataExchangeLogic` and inject proper `DataExchangeLogic` as the member and access through this member. – Horsing Mar 09 '17 at 09:27
  • @GeminiKeith Can you please help with a small example on how to achieve this? Thanks so much – Harry Mar 09 '17 at 19:30
  • Please post code where you invoke save() . I don't see any log saying anything about new transaction created by WebSphereUowTransactionManager – Sergii Shevchyk Mar 09 '17 at 21:56
  • ``` @Component public class DataExchangeLogicWrapper { @Autowired private DataExchangeLogic exchange; public ReturnType handle (ParameterType pt) { return exchange.handle(pt); } } //annotations public class UserCase { @Autowired private DataExchangeLogic exchange; public ReturnType handle (ParameterType pt) { return exchange.handle(pt); } } ``` => ``` //annotations public class UserCase { @Autowired private DataExchangeLogicWrapper exchange; public ReturnType handle (ParameterType pt) { return exchange.handle(pt); } } ``` – Horsing Mar 10 '17 at 09:18
  • Sorry for poor code preview. Please paste this information in your IDE and see what difference. My suggestion is trying to access `dao` by it's wrapper, which actually access methods in `dao` instead you directly call them. By this way, you can make sure whether other framework result in invalidation of `@Transactional`. BTW, if you could post your source code that would do more favor. – Horsing Mar 10 '17 at 09:22
  • @shevchyk ... Will post shortly – Harry Mar 10 '17 at 15:04
  • @GeminiKeith ...I'm implementing your changes.......So I assume Controller --> Service Logic --> Wrapper --> DAO, right? Why is the ServiceLogic injected in the wrapper? Should the controller be calling the wrapper? – Harry Mar 10 '17 at 15:06
  • @shevchyk ...Edited the post to add releavnt code...please let know if I still miss anything – Harry Mar 10 '17 at 19:59
  • Why does the log show UnexpectedRollbackException from JMS transaction first and then the SQL logs? Aren't there relevant logs after the SQL transactions? Please add that too. If you get a UnexpectedRollbackException after the SQL transactions, it is expected. It also means Spring the DAO and JMS are using separate physical transactions, even though they are the same logical transaction. – Shankar Mar 10 '17 at 20:14
  • You don't open new transaction, so insert is executed non-transactionally – Sergii Shevchyk Mar 10 '17 at 22:53
  • Yes, you're right. Few days ago, I encountered a same problem, which caused by my RPC framework and the `@Transactional` won't work. So this is my idea. Why injection, because I need to keep the `@Transactional` taking effect. Assume that you are using Controller -> Service -> Dao, that's should not have problem. Must be something else. I think my answer here will not solve your problem. Source code could help if you do not mind to post it. Otherwise, it's hard to say what's going wrong. – Horsing Mar 13 '17 at 01:17
  • @GeminiKeith..Thanks. I think I added all relevant source code except maybe the spring config xml. Can you please let know what code can I add to help solve the issue. – Harry Mar 13 '17 at 14:20

3 Answers3

6

Please put transactionManager configuration and tx:annotation-driven into root spring context

enter image description here

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.

Sergii Shevchyk
  • 38,716
  • 12
  • 50
  • 61
  • 2
    Wow!. I think this might have fixed my issue. I no longer have new rows inserted in the DB. But still have the JMS rollback issue – Harry Mar 13 '17 at 14:44
  • Harry, your question was about getting transaction rolled back. If you want to raise a new one - just write it – Sergii Shevchyk Mar 16 '17 at 14:58
2

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

Community
  • 1
  • 1
ritesh.garg
  • 3,725
  • 1
  • 15
  • 14
2

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.

VimalKumar
  • 1,611
  • 1
  • 14
  • 11