Spring 事务管理的一个 trick

标签: 框架/架构 Spring 事务 | 发表时间:2017-09-13 15:16 | 作者:coderbee
出处:https://coderbee.net

问题

最近有同事碰到这个异常信息: Transaction rolled back because it has been marked as rollback-only ,异常栈被吃了,没打印出来。

调用代码大概如下:

  @Component
public class InnerService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(rollbackFor = Throwable.class)
    public void innerTx(boolean ex) {
        jdbcTemplate.execute("insert into t_user(uname, age) values('liuwhb', 31)");
        if (ex) {
            throw new NullPointerException();
        }
    }

}

@Component
public class OutterService {
    @Autowired
    private InnerService innerService;

    @Transactional(rollbackFor = Throwable.class)
    public void outTx(boolean ex) {
        try {
            innerService.innerTx(ex);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

outterService.outTx(true);

他期望的是 innerService.innerTx(ex); 调用即使失败了也不会影响 OutterService.outTx 方法上的事务,只回滚了 innerTx 的操作。

结果没有得到他想要的,调用 OutterService.outTx 的外围方法捕获到了异常,异常信息是 Transaction rolled back because it has been marked as rollback-onlyoutTx 的其他操作也没有提交事务。

分析

上述方法的事务传播机制的默认的,也就是 Propagation.REQUIRED,如果当前已有事务就加入当前事务,没有就新建一个事务。

事务性的方法 outTx 调用了另一个事务性的方法 innerTx 。调用方对被调用的事务方法进行异常捕获,目的是希望被调用方的异常不会影响调用方的事务。

但还是会影响调用方的行为的。Spring 捕获到被调用事务方法的异常后,会把事务标记为 read-only,然后调用方提交事务的时候发现事务是只读的,就会抛出上面的异常。

解决方法

网上有人把 AbstractPlatformTransactionManager.globalRollbackOnParticipationFailure 属性设置为 false,说也把问题解决了。

仔细看了更新这个字段的方法 AbstractPlatformTransactionManager.setGlobalRollbackOnParticipationFailure 上的注释发现修改这个字段并不是解决上面的场景的最佳做法,反而可能引入坑。

注释大意如下:

  • 默认是 “true”:如果一个参与事务(例如,标记为的 PROPAGATION_REQUIRESPROPAGATION_SUPPORTS 碰到一个已有事务时就是参与事务)失败,事务将被全局地标记为 rollback-only 。这个事务的唯一结果就是回滚:事务发起者再也不能提交事务。
  • 切换为 “false” 可以让事务发起者决定是否回滚。如果参与事务因为异常失败,调用者仍然可以决定继续走事务内的不同路径。然而,这只有在所有参与资源都允许继续直到事务提交,即使数据访问失败。
  • Note:This flag only applies to an explicit rollback attempt for a subtransaction, typically caused by an exception thrown by a data access operation (where TransactionInterceptor will trigger a PlatformTransactionManager.rollback() call according to a rollback rule). If the flag is off, the caller can handle the exception and decide on a rollback, independent of the rollback rules of the subtransaction. This flag does, however, not apply to explicit setRollbackOnly calls on a TransactionStatus, which will always cause an eventual global rollback (as it might not throw an exception after the rollback-only call).
  • 处理子事务失败的推荐方案是 嵌套事务:全局事务可以回滚到子事务开始的安全点。 PROPAGATION_NESTED 提供了这种语义。当然,这个只有在使用支持嵌套事务的 DataSourceTransactionManager 时生效, JtaTransactionManager 不支持嵌套事务。

之所以说修改这个字段可能引入坑是因为:容易让人误以为 innerTx 抛出异常后,它做的操作就被回滚了,其实是没有的,这些操作会跟随 outTx 上的事务一起提交。也就是说 innerTx 里的操作可能只完成了一部分,这就破外了事务的完整性。

innerTx 标记为 @Transactional(propagation = Propagation.NESTED) 可以保证 innerTx 里操作的事务完整性。

这其实是个嵌套事务的处理场景。

其他的测试代码:

  DROP TABLE IF EXISTS t_user;
create table t_user(uid int auto_increment , uname VARCHAR(100), age int, PRIMARY KEY(uid) ) ENGINE = INNODB default CHARSET UTF8;
  @SpringBootApplication
public class TxApp {
    private static Logger logger = LoggerFactory.getLogger(TxApp.class);

    @Bean
    public PlatformTransactionManager initTransactionManager(DataSource ds) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(ds);
        transactionManager.setGlobalRollbackOnParticipationFailure(false);
        return transactionManager;
    }

    public static void main(String[] args) {
        try (ConfigurableApplicationContext ctx = SpringApplication.run(TxApp.class, args)) {
            OutterService outterService = ctx.getBean(OutterService.class);
            // outterService.outTx(false);
            outterService.outTx(true);
            logger.info("outTx commit success .");
        }
    }

}

相关 [spring 管理 trick] 推荐:

Spring 事务管理的一个 trick

- - 码蜂笔记
最近有同事碰到这个异常信息: Transaction rolled back because it has been marked as rollback-only ,异常栈被吃了,没打印出来. 他期望的是 innerService.innerTx(ex); 调用即使失败了也不会影响 OutterService.outTx 方法上的事务,只回滚了 innerTx 的操作.

spring的事务管理笔记

- - 企业架构 - ITeye博客
首先,事务管理是保证数据操作的事务性 ACID(即原子性、一致性、隔离性、持久性).        对于使用支持事务管理的数据库时,普通的jdbc的连接没用配置事务也可以保存变更,原因在于连接的属性autocommit设置了true,即自动提交了事务.        而对于某些数据库本身没有对事务的支持,那么事务管理也是一纸空谈没有必要进行配置(有例外),如MySQL的MyISAM没有事务管理的支持.

spring+hibernate+atomikos 分布式事务管理

- - 企业架构 - ITeye博客
网上有很多的atomikos的分布式事务管理的配置,但是大多数都是同一类型的数据库,并没有跨数据库类型的配置. 使用的数据库是Oracle和mysql. . .

Spring+quartz 实现动态管理任务

- - 寒江孤影
在实际项目应用中经常会用到定时任务,可以通过quartz和spring的简单配置即可完成,但如果要改变任务的执行时间、频率,废弃任务等就需要改变配置甚至代码需要重启服务器,这里介绍一下如何通过quartz与spring的组合实现动态的改变定时任务的状态的一个实现. 本文章适合对quartz和spring有一定了解的读者.

Trick or Treat? 万圣节各大搜索引擎变身!

- 消逝の航迹云 - cnBeta.COM
吸血鬼和蝙蝠,杰克灯和女巫,这些元素预示着一年一度的搞怪的万圣节到啦. 各大搜索引擎也纷纷换上了稀奇古怪的装扮,来配合鬼魅的节日氛围. 下面我们立即来看一下万圣节各大搜索引擎的logo或皮肤吧~.

使用Java注解进行Spring bean管理

- - 编程语言 - ITeye博客
原文链接: http://www.ibm.com/developerworks/cn/webservices/ws-springjava/. 使用 Java 配置进行 Spring bean 管理. 学习使用 Java 配置管理 Spring bean. Spring bean 是使用传统的 XML 方法配置的.

Spring声明式事务管理与配置介绍

- - 行业应用 - ITeye博客
资料来源:http://java.9sssd.com/javafw/art/1215. 一、Spring声明式事务配置的五种方式. 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spring的事务配置虽说也配置过,但是一直没有一个清楚的认识. 通过这次的学习发觉Spring的事务配置只要把思路理清,还是比较好掌握的.

Spring 事务管理高级应用难点剖析--转

- - 编程语言 - ITeye博客
Spring 最成功,最吸引人的地方莫过于轻量级的声明式事务管理,仅此一点,它就宣告了重量级 EJB 容器的覆灭. Spring 声明式事务管理将开发者从繁复的事务管理代码中解脱出来,专注于业务逻辑的开发上,这是一件可以被拿来顶礼膜拜的事情. 但是,世界并未从此消停,开发人员需要面对的是层出不穷的应用场景,这些场景往往逾越了普通 Spring 技术书籍的理想界定.

Spring中实现多数据源事务管理 - CSDN博客

- -
由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对. @Transactional注解有了进一步的理解(但实际上也并不是非常深入). 然而这是一个演进的过程,刚开始项目中并没有使用. @Transactional指定具体的. TransactionManager,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于.

使用Spring进行统一日志管理 + 统一异常管理

- - 编程语言 - ITeye博客
统一日志和异常管理配置好后,SSH项目中,代码以往散落的log.info() 和 try..catch..finally 再也不见踪影.  * 由Spring AOP调用 输出异常信息,把程序异常抛向业务异常 .         // 在后台中输出错误异常异常信息,通过log4j输出.         // 在这里判断异常,根据不同的异常返回错误.