Spring Service @Transactional doesn't rollback transaction Mybatis SqlSession
Asked Answered
O

3

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);
}
Oration answered 7/3, 2017 at 14:51 Comment(14)
Did your db support transaction?Designed
Oracle is the DB. ThanksOration
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.Antherozoid
@GeminiKeith Can you please help with a small example on how to achieve this? Thanks so muchOration
Please post code where you invoke save() . I don't see any log saying anything about new transaction created by WebSphereUowTransactionManagerParaph
``` @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); } } ```Antherozoid
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.Antherozoid
@shevchyk ... Will post shortlyOration
@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?Oration
@shevchyk ...Edited the post to add releavnt code...please let know if I still miss anythingOration
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.Carse
You don't open new transaction, so insert is executed non-transactionallyParaph
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.Antherozoid
@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.Oration
P
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.

Paraph answered 10/3, 2017 at 23:12 Comment(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 issueOration
Harry, your question was about getting transaction rolled back. If you want to raise a new one - just write itParaph
S
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

Snodgrass answered 9/3, 2017 at 21:4 Comment(4)
@Transactional is defined on service layerParaph
That is true but mybatis operations are happening in the dao method. In order to make that method participate in the transactional behavior, it needs to respect the transactional requirementsSnodgrass
no need to be public for create() because transaction is being opened before entering save in service layerParaph
@Snodgrass - No improvement/change on changing the method visibility to publicOration
G
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.

Guiscard answered 11/3, 2017 at 19:56 Comment(4)
They both print trueOration
Looks like DAO class stars a new transaction. what do u see in the log for log4j.logger.org.springframework.transaction.interceptor = trace . Does it start two transactions or just one.Guiscard
We use logback.xml. can i add the same line there?Oration
Add this and see. <logger name="org.springframework.transaction.interceptor" level="TRACE"/>Guiscard

© 2022 - 2024 — McMap. All rights reserved.