Spring Service @Transactional无法回滚Mybatis SqlSession事务

10

目标是在失败的情况下回滚所有/任何事务。但是这并没有像预期的那样工作。

我们使用Spring MVC + JMS + Service + Mybatis。在日志中,JMS被设置为回滚,但是行被插入而不是回滚。想知道我缺少了什么或者做错了什么?

最近添加了@Transactional标签。所以不确定它是否按预期工作。

代码:

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上下文

<bean id="dataExchLogic"  class="com.abc.service.logic.DataExchLogic">
        <property name="dataExchDao" ref="dataExchDao" />
</bean>

EAR/WAR 项目 Spring 上下文

<!-- Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager" />

<tx:annotation-driven transaction-manager="transactionManager" />

日志:

[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]

编辑1:

控制器代码:

@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已更改:

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);
}

你的数据库支持事务吗? - Blank
Oracle是数据库。谢谢。 - Harry
1
请确保您的 @Transactional 服务不会被其他框架代理。根据您提供的信息,我猜测 DataExchangeLogic 已经被 JMS 框架代理,这将导致 @Transactional 不起作用。我建议您包装您的服务 DataExchangeLogic 并注入适当的 DataExchangeLogic 作为成员,并通过该成员访问。 - Horsing
请发布调用save()的代码。我没有看到任何日志,说明WebSphereUowTransactionManager创建了新的事务。 - Sergii Shevchyk
为什么日志首先显示JMS事务的UnexpectedRollbackException,然后才是SQL日志?SQL事务之后没有相关日志吗?请添加相关日志。如果在SQL事务之后出现UnexpectedRollbackException,则是预期的。这也意味着Spring DAO和JMS正在使用不同的物理事务,即使它们是相同的逻辑事务。 - Shankar
显示剩余9条评论
3个回答

6
请将transactionManager配置和tx:annotation-driven放入根Spring上下文中。

enter image description here

规则:根上下文可以看到Spring创建的所有bean。子上下文(任何Web上下文)只能看到它自己的bean。
在这种情况下,tx:annotation-driven 在Web上下文中查找带有@Transactional 注释的bean。它找不到任何内容,因为您在根上下文中定义了dataExchLogic。这就是为什么您没有任何事务行为的原因。 @EnableTransactionManagement 只查找在同一应用程序上下文中定义的bean上的 @Transactional。这意味着,如果您将注释驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,则仅会在控制器中检查 @Transactional bean,而不是服务。有关更多信息,请参见“DispatcherServlet”第21.2节。
解决方案是将 tx:annotation-driven 移动到根上下文中,因为Root Context可以找到在根或任何Web上下文中定义的任何bean。

2
哇!我认为这可能已经解决了我的问题。我不再有新行插入到数据库中了。但是仍然存在JMS回滚问题。 - Harry
Harry,你的问题是关于如何让事务回滚。如果你想提出新问题——请写下来。 - Sergii Shevchyk

2
从Spring文档中引用:
您可以在接口定义、接口上的方法、类定义或类上的公共方法之前放置@Transactional注释。但是,仅仅存在@Transactional注释是不足以激活事务行为的。@Transactional注释只是元数据,可以被一些运行时基础设施消耗,这些基础设施是@Transactional-aware的,并且可以使用元数据来配置适当的bean以具有事务性行为。在前面的例子中,元素打开了事务行为。
这意味着:
void create(DataExch dataExch);
应该是 public void create(DataExch dataExch);
如果未在公共方法上应用@Transactional注释,则不会展示其行为。
编辑:
由于我的答案被投票否决,为了支持我的答案并阐明当一个带有@Transactional注释的方法调用一个没有注释的方法时的事务行为,请看这个:

在调用另一个没有 @Transactional 注释的方法的 @Transactional 方法中会发生什么? 特别是 Arun P Johny 的答案


@Transactional 定义在服务层。 - Sergii Shevchyk
这是正确的,但是mybatis操作是在dao方法中进行的。为了使该方法参与事务行为,它需要遵守事务要求。 - ritesh.garg
1
无需在create()中公开,因为在进入服务层的save之前会打开事务。 - Sergii Shevchyk
@ritesh.garg - 将方法可见性更改为public后没有改进/变化。 - Harry

2
我认为有两种可能性。 1)您的DAO类正在启动新事务。 2)您的DAO类没有参与事务。
我没有看到任何其他原因会导致数据被更新到数据库中。您可以添加以下属性到log4j以查看有多少个事务正在启动。
log4j.logger.org.springframework.transaction.interceptor = trace

同时,在Service和DAO方法中检查以下事务状态,以确定事务是否处于活动状态。
TransactionSynchronizationManager.isActualTransactionActive()

请告诉我们发生了什么。


它们都打印出true。 - Harry
看起来DAO类开始了一个新的事务。在log4j.logger.org.springframework.transaction.interceptor = trace的日志中,你能看到什么?它是启动了两个事务还是只有一个事务? - VimalKumar
我们使用logback.xml。我可以在那里添加相同的行吗? - Harry
添加这个并查看:<logger name="org.springframework.transaction.interceptor" level="TRACE"/> - VimalKumar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接