Spring事务原理 二

news/2025/2/23 5:26:45

在上一篇博文《Spring事务原理 一》中,我们熟悉了Spring声明式事务的AOP原理,以及事务执行的大体流程。

本文中,介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中,我们将结合案例,来讲解实战中有关事务的易错点。

本文中源码来自Spring 5.3.x分支,github源码地址:GitHub - spring-projects/spring-framework: Spring Framework

一 Spring事务的核心组件

了解相关类和接口,看看Spring对概念、术语是如何封装的?

1.1 PlatformTransactionManager

事务管理器接口,负责获取数据库连接,事务的开启、提交和回滚。

有抽象实现AbstractPlatformTransactionManager,其中定义了doGetTransaction、doBegin、doCommit、doRollback等抽象方法,待子类实现。

AbstractPlatformTransactionManager中有几个值得关注的属性:

java">// 是否允许嵌套事务
private boolean nestedTransactionAllowed = false;

// 局部失败时全局回滚,当为false时,部分失败则不回滚
private boolean globalRollbackOnParticipationFailure = true;

private boolean failEarlyOnGlobalRollbackOnly = false;

private boolean rollbackOnCommitFailure = false;

它有以下常见子类:

  • DataSourceTransactionManager:用于JDBC和MyBatis等基于数据源的事务管理。
  • HibernateTransactionManager:用于Hibernate框架的事务管理。
  • JpaTransactionManager:用于JPA(Java Persistence API)的事务管理。

DataSourceTransactionManager

该类中定义了两个属性,对doCommit等抽象方法提供实现。

  • doGetTransaction方法:创建一个DataSourceTransactionObject对象,设置connection。

  • doBegin方法:执行事务前的准备工作,如设置
    • 如果DataSourceTransactionObject没有连接,则获取一个连接
    • 根据TransactionDefinition,为connection设置属性,如isolationLevel、readOnly、timeout;
    • connection.setAutoCommit(false),关闭自动提交;
    • 将connection与当前线程绑定;
  • doCommit方法:从TransactionStatus中获取TransactionObject,拿到connection调用commit();

  • doRollback方法:与doCommit实现相似,只是调connection.rollback();

1.2 TransactionDefinition

定义事务的属性,如隔离级别、传播行为、超时时间等。

隔离级别(Isolation Level):定义了事务之间的隔离程度,常见的有:

  • DEFAULT:使用数据库默认的隔离级别。
  • READ_UNCOMMITTED:允许读取未提交的数据,可能导致脏读。
  • READ_COMMITTED:只能读取已提交的数据,避免脏读。
  • REPEATABLE_READ:确保在同一事务中多次读取同一数据时,结果一致。
  • SERIALIZABLE:最高的隔离级别,确保事务串行执行,避免脏读、不可重复读和幻读。

超时时间(Timeout):事务的超时时间,超过该时间未完成则自动回滚。

只读(Read-only):指定事务是否为只读事务,优化性能。

在子类DefaultTransactionDefinition中,可以看到默认值:PROPAGATION_REQUIRED、ISOLATION_DEFAULT、TIMEOUT_DEFAULT、非readOnly。

当使用@Transactional时,会将注解属性解析成一个TransactionDefinition对象。

1.3 TransactionStatus

表示事务的状态,提供了以下方法:

  • isNewTransaction():判断当前事务是否为新事务。
  • hasSavepoint():判断是否存在保存点(用于嵌套事务)。
  • setRollbackOnly():标记事务为回滚状态。
  • isRollbackOnly():判断事务是否被标记为回滚。

在子类DefaultTransactionStatus中,有这些属性

java">private boolean rollbackOnly = false;

private boolean completed = false;

private final Object transaction;

private final boolean newTransaction;

private final boolean newSynchronization;

private final boolean readOnly;

1.4 TransactionSynchronizationManager

事务同步管理器,用于将事务相关信息与当前线程绑定,以支持各种事务传播行为。其中有多个ThreadLocal属性。

为什么保存连接的resources是Map类型?因为支持多数据源,当一个方法中操作多个数据库时,线程中就得保存多个connectionHolder对象,因此使用Map结构,key就是dataSource对象。

1.5 TransactionInterceptor

事务拦截器,就是AOP的代理逻辑,具体实现在TransactionAspectSupport#invokeWithinTransaction中。

大体流程为:

  1. 获取当前方法的@Transaction注解属性,创建TransactionDefinition对象;
  2. 获取TransactionManager对象;
  3. 根据方法名生成事务名;
  4. 如有必要则创建事务,并处理传播行为;
  5. 在try中执行下一个interceptor或被代理对象中方法;
  6. 异常时先回滚事务,正常时提交事务;
  7. 当前方法执行结束,还原TransactionInfo(恢复上层方法的事务信息)。

二 事务的传播机制

2.1 什么是事务传播

在日常开发中,业务代码中经常出现方法间调用,比如购物时下单减和库存:

java">import com.xiakexing.dao.InventoryDao;
import com.xiakexing.dao.OrderDao;
import com.xiakexing.entity.Order;

public class OrderService {

    private OrderDao orderDao;
    private InventoryDao inventoryDao;

    public void saveOrder(Order order) {
        orderDao.save(order);
        updateInventory(order.getCode(), order.getCount());
    }

    public void updateInventory(String code, int count) {
        inventoryDao.update(code, count);
    }
}
  1. saveOrder()和updateInventory(),所有sql需要在同一个事务中;
  2. 单独调用updateInventory()时,如进货时不需要事务。

可见,updateInventory方法,在不同场景下对事务有不同要求。Spring中又如何实现呢?

Spring定义了传播行为(Propagation Behavior),定义了方法间调用时事务如何传递,类型有:

  • REQUIRED:如果当前线程存在事务,则加入该事务;如果不存在,则创建一个新事务。
  • REQUIRES_NEW:总是创建一个新事务,如果当前线程存在事务,则挂起当前事务。
  • SUPPORTS:如果当前线程存在事务,则加入该事务;如果不存在,则以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行,如果当前线程存在事务,则挂起当前事务。
  • MANDATORY:如果当前线程存在事务,则加入该事务;如果不存在则抛出异常。
  • NEVER:以非事务方式执行,如果当前线程存在事务,则抛出异常。
  • NESTED:如果当前线程存在事务,则以嵌套事务中执行;如果不存在,则创建一个新事务。

这儿为什么强调线程呢?

方法间调用都在某个线程的方法栈中,按FILO顺序执行。如果两个方法的中sql使用两个不同的数据库连接执行,显然无法纳入一个事务中。

因为,数据库连接必须能够跨方法传递,Spring底层就是将connection放到ThreadLocal中。

2.2 传播机制的实现

2.2.1 线程绑定连接

在DataSourceTransactionManager#doBegin中:

  • 从DataSource获取数据连接connection,设置autocommit=false、隔离级别、超时时间等属性;
  • 将connection放入ThreadLocal<Map>,Map的key是DataSource对象,value是connectionHolder对象。

可见,方法间调用时可以从ThreadLocal中拿到同一个连接,去执行不同的SQL,进而一同提交或回滚。

2.2.2 处理传播机制

真正执行被代理对象方法前,会判断是否创建事务。

调用AbstractPlatformTransactionManager#getTransaction,逻辑如下:

  1. 创建DataSourceTransactionObject对象,从ThreadLocal中获取connectionHolder(可能为null);
  2. 当connectionHolder不为null且connectionHolder.transactionActive=ture时,说明已存在事务:

如果当前线程中存在事务:

  • 如果当前方法传播行为是PROPAGATION_NEVER,则抛异常
  • 如果是PROPAGATION_NOT_SUPPORTED,则挂起当前事务,用一个新连接的非事务方式执行当前方法;
  • 如果是PROPAGATION_REQUIRES_NEW,则挂起当前事务,开启一个新事务(获取新连接并带事务执行);
  • 如果是PROPAGATION_NESTED,则先设置savepoints(可以回滚到此处),然后使用同一个连接继续执行。

如果当前线程中不存在事务:

  • 如果传播行为是PROPAGATION_MANDATORY,则抛异常
  • 如果传播行为是PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED,则开启事务

流程图如下:

三 总结

  1. Spring中,对于事务这一抽象概念,从多个方法进行了良好封装,如将隔离级别、超时时间等封装为TransactionDefinition,将事务状态、是否回滚等封装为TransactionStatus。
  2. 事务的传播行为,发生在方法间调用中。通过将connectionHolder放入ThreadLocal,实现了不同方法中使用同一数据库连接,从而支持多种传播方式。
  3. 事务底层,就是通过设置connection.autocommit为false,从而根据方法是否异常,选择commit还是rollback;
  4. 通过Savepoint实现嵌套事务(需要数据库支持)。
  5. 在执行某个方法时,判断当前是否已经存在事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象。

http://www.niftyadmin.cn/n/5862990.html

相关文章

记录首次安装远古时代所需的运行环境成功npm install --save-dev node-sass

最开始的报错&#xff1a; 最后根据报错一步步 安装所需要的pythong之类的环境&#xff0c;最后终于成功了&#xff0c;得以让我在github上拉的vuehr项目&#xff08;狗头18年还是20年的远古项目&#xff09;成功本地运行&#xff0c;最后附上本地运行成功的贴图。如果大家也在…

如果后台的Long类型的数据返回是null,那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗?

如果后台的Long类型的数据返回是null&#xff0c;那么Android客户端的数据bean的kotlin的Long类型的字段接受到数据后是null空指针吗&#xff1f; DeepSeek R1 思考 35 秒 思考过程 好的&#xff0c;用户的问题是关于在Android客户端使用Kotlin处理后台返回的Long类型数据为n…

C++ IDE设置 visual studio 2010安装、注册、使用

Visual Studio 2010 C学习版 系列教程_哔哩哔哩_bilibiliVisual Studio 2010 C学习版 系列教程共计16条视频&#xff0c;包括&#xff1a;Visual Studio C 2010学习版 安装教程、Visual Studio C 2010学习版 激活方法、Visual Studio C 2010学习版 软件使用教学等&#xff0c;U…

分类解析决策模型

一、按理性程度划分的决策模型 1. 完全理性模型(古典决策理论) 核心假设:决策者拥有完全信息,能计算所有选项的效用并选择最优解。数学模型: 期望效用理论(Expected Utility Theory, EU): E U = ∑ ( p i

微信小程序开发中CSS书写常见问题及最佳实践

在微信小程序中&#xff0c;存在一些CSS选择器的限制和特殊性。如后代选择器&#xff08;如 view button&#xff09;在微信小程序中可能无法按预期工作&#xff0c;我设置width改变按钮的默认宽度效&#xff0c;而更具体的选择器&#xff08;如 view button.btn&#xff09;则…

LeetCode 热题 100_在排序数组中查找元素的第一个和最后一个位置(65_34_中等_C++)(二分查找)(一次二分查找+挨个搜索;两次二分查找)

LeetCode 热题 100_在排序数组中查找元素的第一个和最后一个位置&#xff08;65_34&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;一次二分查找挨个搜索&#xff09;&#xff1a;思路二&#xff08;两次二…

基于腾讯云大模型知识引擎×DeepSeek构建八字、六爻赛博算卦娱乐应用

引言 随着DeepSeek的火爆&#xff0c;其强大的思维链让不少人越用越香&#xff0c;由于其缜密的思维和推理能力&#xff0c;不少人开发出了不少花里胡哨的玩法&#xff0c;其中一种就是以八字、六爻为代表的玄学文化正以“赛博玄学”的新形态席卷年轻群体。 针对于八字、六爻…

RAG检索中使用一个 长上下文重排序器(Long Context Reorder) 对检索到的文档进行进一步的处理和排序,优化输出顺序

使用一个长上下文重排序器对检索到的文档进行进一步的处理和排序&#xff0c;优化输出顺序。 优化前的检索内容&#xff0c;顺序混乱&#xff1a; 优化后的检索内容&#xff0c;按顺序排列&#xff1a; 使用 LongContextReorder 对检索得到的文档进行重新排序。这一步的目的…