type
status
date
slug
summary
tags
category
icon
password
Imspring 源码介绍(五):事务实现
Imspring 项目地址点这里

基本概念

事务(Transaction)是数据库系统执行过程中的一个逻辑处理单元,可由一条简单的 SQL 语句组成,也可以由一组复杂的 SQL 语句组成,这些 SQL 语句要么都成功,要么都失败,这就是事务的目的。
Spring 事务依赖于底层数据库对事务的支持,如果底层数据库不支持事务,Spring 事务就会失效。Spring 事务基于数据库事务提供一套抽象的事务管理接口,底层由各数据库自己实现,并结合 Spring AOP 实现声明式事务,可以做到对程序无侵入实现事务功能,简化了程序使用事务的步骤。
在介绍 Spring 事务之前,先简单事务的一些基本概念。

事务ACID特性

事务应该满足 ACID 特性,以 Mysql 的 InnoDB 引擎为例:
  • 原子性(Atomicity):事务是不可分割的最小单位,整个事务要么一起成功,要么一起失败。原子性通过 undo log 来保证
  • 一致性(Consistency):事务使得系统从一个一致的状态转换到另一个一致状态,事务执行结束后,数据库的完整性约束没有被破坏。一致性靠其他三个特性来保证
  • 隔离性(Isolation):一个事务内部对其他事务是隔离的,一个事务所做的修改在最终提交以前,对其他事务是不可见的。隔离性通过锁(写写)或者 MVCC(写读)来保证
  • 持久性(Durability)事务一旦提交,那么就是永久性的,不会因为宕机等故障导致数据丢失。持久性通过 binlog、redo log 来保证

事务隔离级别

多个事务在并发执行的过程,当前事务的执行结果是否对其他事务可见、什么时候可见?这个问题会引发下面的可见性问题。按问题的严重程度排序是:脏写 > 脏读 > 不可重复读 > 幻读
  • 脏写:也称为数据丢失、更新丢失,简单来说就是一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致,属于最严重的问题。一般有两种情况:
    • 事务 A 的回滚覆盖了事务 B 已提交的修改,造成事务 B 的修改丢失。
    • 事务 A 的提交覆盖了事务 B 已提交的修改,造成事务 B 的修改丢失。
  • 脏读:一个事务读到了另一个事务修改过但是未提交的数据。
notion image
  • 不可重复读:在没有脏读的情况下,因为其他事务多次修改数据并提交,一个事务多次读取同一个数据不一致。也就是同一个数据无法重复读取,违反了数据库事务一致性的要求。
notion image
其实不可重复读在一些场景下也不是问题,比如我就希望在一个事务中,别的事务修改提交数据后,我立马也能读到,那就需要不可重复读。
  • 幻读:一个事务用同样的条件查询,由于另一个事务新增了数据,导致看到了之前没有的数据。幻读和不可重复读的区别是:不可重复读针对 update,幻读针对 insert。
notion image
上面的四个问题,脏写可以通过乐观锁或悲观锁的方式来解决,剩下的3个问题,其实是数据库读一致性造成的。SQL 标准定义了四种事务隔离机制,用来解决事务的可见性问题。
隔离级别
定义
解决问题
未提交读(READ UNCOMMITTED,简称RU)
事务中的修改,即使没有提交,对其他事务也是可见的。
会发生脏读、不可重复读、幻读的问题
提交读(READ COMMITTED,简称RC)
一个事务可以读取其他已经提交的事务所做的修改。也就是说在事务处理期间,如果其他事务对数据进行多次修改提交,那么当前事务每次读取到的值可能都不一样。
解决了脏读的问题,会发生不可重复读、幻读的问题。
可重复读(REPEATABLE READ,简称RR)
保证在同一个事务中多次读取同样数据的结果是一样的。也就是说在事务处理期间,如果其他事务对数据进行多次修改提交,也能保证当前事务每次读到的数据都是一致的;但是如果其他事务插入了新的数据,当前事务还是会出现前后读到不同数据的情况,会发生幻读的问题。
解决了脏读、不可重复读的问题,会发生幻读的问题
可串行化(SERIALIZABLE)
强制事务串行执行,需要加锁实现。
脏读、不可重复读、幻读的问题都不会发生
Spring 比 Mysql 多了一种隔离级别 default,表示使用数据库默认的事务隔离级别。

事务传播类型

Spring 事务传播类型是 Spring 为了业务层之间调用事务的关系而提出的,即数据库层面是不存在事务传播行为的。Spring 中存在 7 种传播类型。
  • REQUIRED:如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务。Spring 默认的事务的传播类型。
  • SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行。
  • MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
  • REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。新事务和原事务是两个独立的事务。
  • NOT_SUPPORTED:始终以非事务方式执行,如果当前存在事务,则挂起当前事务。
  • NEVER:不使用事务,如果当前事务存在,则抛出异常。
  • NESTED:如果当前事务存在,则在嵌套事务中执行,否则和 REQUIRED 一样开启一个事务。

Spring 事务组件

Spring 事务管理抽象主要包括 3 个接口:
  • TransactionManager:事务管理器
  • TransactionDefinition:事务定义信息(隔离,传播,超时,只读)
  • TransactionStatus:事务具体运行状态
这三个组件我们一般是这样用的:先把 @Transactional 注解里面的配置解析并封装成 TransactionDefinition 对象,作为参数传递给 PlatformTransactionManagergetTransaction() 方法来开启事务,返回一个 TransactionStatus 对象表示事务运行状态,然后根据 TransactionStatus 的状态(是否 rollbackOnly、Completed 等)来判断是执行 commit() 还是 rollback()

TransactionManager

TransactionManager 接口是个空接口,一般我们会使用子接口 PlatformTransactionManager。PlatformTransactionManager 是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下:
Spring 将事务管理器的实现委托给底层具体的持久化框架来完成。因此,Spring 为不同的持久化框架提供了 PlatformTransactionManager 接口的实现类。
实现类
说明
org.springframework.jdbc.datasource. DataSourceTransactionManager
使用 SpringJDBC 或 Mybatis 等基于 DataSource 数据源的持久化持久时,使用此事务管理器。最常用的事务管理器。
org.springframework.orm.jpa. JpaTransactionManager
使用 JPA 进行持久化时,使用该事务管理器
org.springframework.orm.hibernateX. HibernateTransactionManager
使用 HibernateX.0(X可为3,4,5) 进行持久化时,使用此事务管理器
org.springframework.orm.jdo. JdoTransactionManager
使用 JDO 进行持久化时,使用此事务管理器
org.springframework.transaction.jta. JtaTransactionManager
具有多个数据源的全局事务使用该事务管理器(不管采用何种持久化技术)

TransactionDefinition

TransactionDefinition 接口定义了 Spring 的事务属性以及获取事务属性的方法。一般情况下我们会使用子接口 TransactionAttribute 的实现类,解析 @Transactional 注解里面的配置解析并封装成 TransactionAttribute 对象。

TransactionStatus

TransactionStatus 代表事务的具体运行状态,可以通过该接口获取事务运行期的状态信息。

其他组件

除了上面三大组件,Spring 为了实现事务机制还定义了一些其他组件。

TransactionInterceptor

TransactionInterceptor 可以看作一种特殊的 AOP Advice,专门用来处理 Spring 事务。TransactionInterceptor 定义了处理事务的主流程,是 Spring 声明式事务的核心类。

TransactionAttributeSource

上面提到 Spring 容器在启动的时候就会解析所有 @Transaction 方法,并封装为 TransactionAttribute 对象。TransactionAttributeSource 就是用来缓存这些 TransactionAttribute 对象的。
TransactionAttributeSource 的实现类有一个属性 attributeCache 缓存了所有 TransactionAttribute 对象,当某个 @Transaction 方法被调用的时候,从这里获取 TransactionAttribute。

TransactionAttributeSourceAdvisor

TransactionAttributeSourceAdvisor 可以看作一种特殊的 AOP Advisor。按照 AOP 的定义:Advisor = Advice + Advice Filter(这里的 Advice Filter 是 Pointcut),所以 TransactionAttributeSourceAdvisor = TransactionInterceptor + TransactionAttributeSourcePointcut
TransactionInterceptor 的作用上面已经解释过,TransactionAttributeSourcePointcut 的作用是校验当前方法是否事务方法,原理是使用当前类 + 当前方法作为 key,从 TransactionAttributeSource 获取 TransactionAttribute,如果能获取到说明当前方法是事务方法。

TransactionSynchronization

TransactionSynchronization 是可以注册到事务处理过程中的回调接口。它就像是事务处理的事件监听器,当事务处理的某些规定时点发生时,会调用 TransactionSynchronization 上的一些方法来执行相应的回调逻辑。

TransactionSynchronizationManager

TransactionSynchronizationManager 是用来管理每个线程的 DB 连接资源、事务同步器(TransactionSynchronization)、当前事务各种状态的管理类。TransactionSynchronizationManager 中定义了很多 ThreadLocal 变量用来保存这些信息。
TransactionSynchronizationManager 的作用是:
  1. 屏蔽了底层数据库的差异,统一使用 TransactionSynchronizationManager#getResource() 获取当前的数据库连接资源。
  1. 执行事务同步器的回调。使用 triggerXXX() 之类的方法执行当前事务同步器的方法。

Spring 声明式事务失效

Spring 声明式事务在下面 7 种情形会失效,这里不是文章的重点,简单了解一下即可。
  1. 未启用 Spring 事务管理功能:@EnableTransactionManagement 注解用来启用 Spring 事务自动管理事务的功能。
  1. 方法不是 public 类型的:@Transaction 用在了非 public 方法上,事务将无效。
  1. 数据源未配置事务管理器:Spring 是通过事务管理器 TransactionManager 来管理事务,每个数据源都要配置。
  1. 自身调用问题:@Transactional 基于 AOP 实现事务功能,同类方法调用会导致事务失效,只有在代理对象之间进行调用时,才会触发切面逻辑。可以在类中注入该类代理对象,然后在嵌套的方法中调用代理对象的方法。
  1. 异常类型错误:默认情况下,发生 RuntimeExceptionError 类型及子类的异常,Spring 事务才会回滚。可以自定义回滚的异常类型,@Transactional(rollbackFor = {异常类型列表})
  1. 异常被吞:在方法中 catch 异常后没有向上抛出。
  1. 业务和 Spring 事务代码不在一个线程:Spring 事务实现中使用了 ThreadLocal,要求业务代码和 Spring 事务的代码必须在一个线程中,才会受 Spring 事务的控制。

代码整体流程

notion image
imspring-tx 的声明式事务整体流程如上所示,相较于 Spring 源码流程做了较多的简化,例如只支持新开事务的传播机制、不支持 savepoint 部分回滚等。Imspring-tx 只把声明式事务的主体流程提炼出来,有助于快速入门。整体流程可以分为两步:
  1. 前面提到过事务是基于 AOP 的,所以和 AOP 一样是在 BeanFactory 的前后置处理器里面处理包含 @Transaction 方法的 Bean。这里的 @Transactional 注解相当于一个特殊的 AspectJ 注解,这个注解通过 TransactionAttributeSourceAdvisor来解析的,而 TransactionAttributeSourceAdvisor 实例通过 @Configuration 配置类手动向容器注入。这样在 ApplicationContext 容器启动的时候,就会自动为所有包含 @Transaction 方法的 Bean 创建代理对象。
  1. @Transaction 方法被调用的时候,实际上是调用了上面的代理对象。代理对象会执行拦截器 TransactionInterceptor,这里面定义了事务执行的整体流程,这就是声明式事务的原理。
Jackson使用详解Imspring 源码介绍(四):WebMVC 实现
mcbilla
mcbilla
一个普通的干饭人🍚
Announcement
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎来到飙戈的博客🎉
-- 感谢您的支持 ---
👏欢迎学习交流👏