Spring5源码(49)-@Transactional声明式事物(七)嵌套事物回滚

简介: Spring5源码(49)-@Transactional声明式事物(七)嵌套事物回滚


1.引

上一节分析了嵌套事物的创建,本节分析每种传播特性的回滚处理过程。由于这一部分的组合情况会很多,我们只分析其中的一两种情况,更多的大家还是要多看源码、多测试! 注意:这里最外层的事物一定要开启,如果将最外层的事物特性设置为PROPAGATION_NOT_SUPPORTED,则不会引发嵌套事物的问题。

2.processRollback回顾

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
    try {
        boolean unexpectedRollback = unexpected;
        try {
            // 1.事物完成之前的触发器调用
            triggerBeforeCompletion(status);
            // 2.如果有保存点,则调用rollbackToHeldSavepoint回滚到保存点
            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Rolling back transaction to savepoint");
                }
                status.rollbackToHeldSavepoint();
            }
            // 3.如果当前事物是一个新的事物,则调用doRollback执行给定事物的回滚
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction rollback");
                }
                doRollback(status);
            }
            else {
                // Participating in larger transaction
                // 4.如果当前事物并非独立事物,则将当前事物的rollbackOnly属性标记为true,等到事物链完成之后,一起执行回滚
                // 如果当前存在事物,但是
                // 事物的rollbackOnly属性已经被标记为true
                // 或者globalRollbackOnParticipationFailure(返回是否仅在参与事务失败后才将现有事务全局标记为回滚)为true
                if (status.hasTransaction()) {
                    if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                        }
                        System.out.println("==当前事物并非独立事物,且RollbackOnly为true\n");
                        // 则将ConnectionHolder中的rollbackOnly标记为true
                        doSetRollbackOnly(status);
                    }
                    else {
                        if (status.isDebug()) {
                            logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                        }
                    }
                }
                // 5.如果当前不存在事物,则不会回滚
                // 例如配置了 @Transactional(propagation = Propagation.NOT_SUPPORTED)
                else {
                    logger.debug("Should roll back transaction but cannot - no transaction available");
                }
                // Unexpected rollback only matters here if we're asked to fail early
                if (!isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = false;
                }
            }
        }
        catch (RuntimeException | Error ex) {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            throw ex;
        }
        // 6.事物完成之后的触发器调用
        triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
        // Raise UnexpectedRollbackException if we had a global rollback-only marker
        if (unexpectedRollback) {
            throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
        }
    }
    finally {
        //7.事物完成后清理资源
        cleanupAfterCompletion(status);
    }
}
3.NOT_SUPPORTED

BankService、PersonService、AccountService的事物传播特性依次是:

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void save() throws RuntimeException {
    System.out.println("==调用BankService的save方法\n");
    System.out.println("==准备调用PersonService的save方法\n");
    personService.save();
    System.out.println("==准备调用PersonService的save方法\n");
    accountService.save();
    throw new RuntimeException("==AccountService的save方法手动抛出异常");
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void save() throws RuntimeException {
    System.out.println("==调用PersonService的save方法\n");
    jdbcTemplate.update(insert_sql);
    throw new RuntimeException("==PersonService手动抛出异常");
}
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void save() throws RuntimeException {
    System.out.println("==调用AccountService的save方法\n");
    jdbcTemplate.update(insert_sql);
    throw new RuntimeException("==AccountService的save方法手动抛出异常");
}

该特性下PersonService、AccountService并不会开启事物,在processRollback方法中会走到第6点,所以即便PersonService、AccountService抛出异常,也不会回滚。但是最外层的BankService是开启事物的,所以如果BankService里有针对数据库的写操作并抛出异常,依然会回滚。

但是这里是嵌套事物的回滚,当内层事物回滚之后不要忘记,外层事物还处在被挂起的状态,那么外层被挂起的事物如何恢复呢?就在processRollback方法中的第7点。

4.恢复挂起事物

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    // 1.将当前事物状态标记为已完成
    status.setCompleted();
    // 2.清除synchronization
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }
    // 3.事务完成后清理资源。
    if (status.isNewTransaction()) {
        doCleanupAfterCompletion(status.getTransaction());
    }
    // 4.从嵌套事物中恢复被挂起的资源
    if (status.getSuspendedResources() != null) {
        if (status.isDebug()) {
            logger.debug("Resuming suspended transaction after completion of inner transaction");
        }
        Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
        resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
    }
}

关于其他的资源清理涉及的代码很多,留在后面介绍,这里我们只看如何恢复被挂起的事物。

protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder) throws TransactionException {
    if (resourcesHolder != null) {
        Object suspendedResources = resourcesHolder.suspendedResources;
        if (suspendedResources != null) {
            // 恢复挂起资源
            doResume(transaction, suspendedResources);
        }
        List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
        // 恢复挂起的事物同步回调接口
        if (suspendedSynchronizations != null) {
            TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
            TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
            TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
            TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
            doResumeSynchronization(suspendedSynchronizations);
        }
    }
}
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
    TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}
public static void bindResource(Object key, Object value) throws IllegalStateException {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Assert.notNull(value, "Value must not be null");
    Map<Object, Object> map = resources.get();
    // set ThreadLocal Map if none found
    if (map == null) {
        map = new HashMap<>();
        resources.set(map);
    }
    Object oldValue = map.put(actualKey, value);
    // Transparently suppress a ResourceHolder that was marked as void...
    if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
        oldValue = null;
    }
    if (oldValue != null) {
        throw new IllegalStateException("Already value [" + oldValue + "] for key [" +actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +Thread.currentThread().getName() + "]");
    }
}

该方法比较简单,获取被挂起事物后重新绑定到resources对象即可。resources的定义如下:

private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
5.PROPAGATION_REQUIRES_NEW

该情况下,因为PROPAGATION_REQUIRES_NEW每次都会新建事物,并挂起已有事物,所以在该传播特性下,如果PersonService或AccountService发生异常,就会执行回滚操作。当PersonService或AccountService完成回滚之后,恢复并回滚之前挂起的事物即可。至于外层被挂起的事物会不会回滚,则要根据外层事物的传播特性而定。假如外层事物的传播特性为NOT_SUPPORTED,那么即便外层事物操作了数据库并抛出异常也不会被回滚。而且不会有嵌套事物的问题。

6.PROPAGATION_NESTED

该特性下就有可能会走到processRollback方法的弟2步,即回滚到保存点。前提是外层开启事物,且内层事物必须有异常抛出。针对本例即BankService必须开启事物,PersonService或AccountService抛出异常。如果PersonService或AccountService没有抛出异常,虽然两者存在保存点,但是并不会回滚,这样一来即便BankService抛出异常并回滚也不会回滚到保存点,因为保存点是建立在PersonService和AccountService对应的事物上的。

下面来看回滚到保存点的实现:

/**
 * 回滚到事物的保存点并释放保存点资源
 * Roll back to the savepoint that is held for the transaction
 * and release the savepoint right afterwards.
 */
public void rollbackToHeldSavepoint() throws TransactionException {
    Object savepoint = getSavepoint();
    if (savepoint == null) {
        throw new TransactionUsageException("Cannot roll back to savepoint - no savepoint associated with current transaction");
    }
    // 回滚到保存点
    getSavepointManager().rollbackToSavepoint(savepoint);
    // 释放保存点资源
    getSavepointManager().releaseSavepoint(savepoint);
    setSavepoint(null);
}
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
    // 回滚到保存点并重置RollbackOnly属性
    ConnectionHolder conHolder = getConnectionHolderForSavepoint();
    try {
        conHolder.getConnection().rollback((Savepoint) savepoint);
        conHolder.resetRollbackOnly();
    }
    catch (Throwable ex) {
        throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
    }
}
public void releaseSavepoint(Object savepoint) throws TransactionException {
    ConnectionHolder conHolder = getConnectionHolderForSavepoint();
    try {
        // 释放保存点资源
        conHolder.getConnection().releaseSavepoint((Savepoint) savepoint);
    }
    catch (Throwable ex) {
        logger.debug("Could not explicitly release JDBC savepoint", ex);
    }
}
7.PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED

对于这两种特性,既不会开启一个新的事物,也不会在原有事物中嵌套运行,而是把本身的事物交给已有事物。该情况下会执行到processRollback方法的第4点,将rollbackOnly标记为true,等到最外层事物事物回滚的时候一起回滚。即使最外层的事物没有抛出异常,内层事物的异常也会被外层事物截获并将整个事物进行回滚。

8.总结

关于嵌套事物的处理,就先分析到这里,大家还是要多结合事物创建过程、事物传播特性等多多分析。






目录
相关文章
|
2月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
6月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
398 70
|
11月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
268 2
|
6月前
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
843 12
|
7月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
126 0
|
9月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
317 7
|
10月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
208 2
|
11月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
228 9
|
12月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
733 1
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码