Seata客户端代理增强核心源码解析
文章目录
- 前言
- 一、SeataAutoDataSourceProxyCreator
- 1.1、DataSourceProxy
- 1.2、PreparedStatementProxy
- 二、Seata代理增强
- 2.1、executeAutoCommitFalse
- 2.1.1、生成前置镜像
- 2.1.2、生成后置镜像
- 2.1.3、生成undolog
- 总结
前言
当调用到标注了@GlobalTransactional
注解的方法,最终会执行到io.seata.tm.api.TransactionalTemplate
的execute
方法:
这是一个标准的事务编程模型。rs = business.execute();是执行目标业务代码的逻辑,其中就包含了如何生成前置镜像、后置镜像、记录undolog、向TC注册分支事务的逻辑。
一、SeataAutoDataSourceProxyCreator
在Seata与Spring boot整合过程中,会向容器中条件装配一个SeataAutoDataSourceProxyCreator
类型的Bean。
SeataAutoDataSourceProxyCreator
继承自AbstractAutoProxyCreator
:
重写了父类的wrapIfNecessary
方法,在其中会对普通的数据源进行包装:
根据模式的不同,进行包装,这里的模式是在配置文件中声明的:
以AT模式为例,最终返回的是一个DataSourceProxy
对象:
上述过程是在Spring启动过程中完成的。
1.1、DataSourceProxy
DataSourceProxy
中有一个关键的方法getConnection()
,它会返回一个代理后的proxy:
ConnectionProxy
又继承了AbstractConnectionProxy
,其中有关键方法:createStatement
和prepareStatement
:
prepareStatement
是重写JDBC的方法。如果是在AT模式下:
- 使用 SQLVisitor 工厂获取 SQL 语义识别器。目的是分析这条 SQL 是 INSERT、UPDATE、DELETE 还是其他类型。
- 如果是 INSERT 语句且识别器只有一个,获取目标表的元信息(主键、列等),创建支持主键返回的 PreparedStatement。
- 返回
PreparedStatementProxy
代理对象,以支持 Seata 的事务回滚、日志等机制。
而createStatement
,核心目的是为普通 Statement 加一层代理,以支持事务的拦截与管理。
1.2、PreparedStatementProxy
当执行业务代码的sql时,会执行io.seata.rm.datasource.PreparedStatementProxy
的execute
方法:
在这里会进行一系列的判断:
如果当前没有启用全局锁,且当前分支事务不是 AT 模式,就直接使用原生的 JDBC Statement 执行逻辑,不走 Seata 的增强逻辑。
如果 sqlRecognizers 为空,就重新解析 SQL,SQL Recognizer 是 Seata 内部的语法分析器,用于识别 SQL 的类型和结构(如表名、主键、字段等):
判断 SQL 是否识别成功:如果不能识别 SQL 类型,就走默认执行器 PlainExecutor —— 它不做增强处理,直接执行 SQL。
根据识别出的 SQL 类型选择增强执行器,并且执行上报分支事务,前置镜像,后置镜像,undolog记录的逻辑。多个 SQL 或复杂语句(如 batch SQL)会由 MultiExecutor 处理。
最终执行 SQL:
二、Seata代理增强
在BaseTransactionalExecutor
的execute
方法中,首先会拿到xid并且绑定到当前的链接上。如果某个分支事务,不想加入到Seata的全局事务中,可以在执行自己的数据库操作逻辑前,对xid进行解绑,执行完成数据库操作后,再将xid绑定到当前线程。
拿到代理链接,如果当前事务是自动提交,则会进入executeAutoCommitTrue
分支:
在executeAutoCommitTrue
分支中,有三个重要的方法:
connectionProxy.changeAutoCommit();
设置事务非自动提交executeAutoCommitFalse
获得前置镜像,后置镜像,执行目标sql,以及写入undolog。connectionProxy.commit()
数据库本地提交事务,向TC注册分支事务信息。
2.1、executeAutoCommitFalse
executeAutoCommitFalse
是获得前置镜像,后置镜像,执行目标sql,以及写入undolog的逻辑:
SQL 类型 | 前置镜像(Before Image) | 后置镜像(After Image) | 作用说明 |
---|---|---|---|
INSERT | ❌ 无(插入之前不存在) | ✅ 插入后的记录 | 回滚时删除插入的记录 |
DELETE | ✅ 被删除前的记录 | ❌ 无(删除后记录已不存在) | 回滚时重新插入被删除的数据 |
UPDATE | ✅ 更新前的记录(包括主键、字段值) | ✅ 更新后的记录 | 回滚时根据 PK 恢复原数据 |
SELECT | ❌ 无 | ❌ 无 | 不需要,读取不会产生事务性修改 |
SELECT … FOR UPDATE | ✅(用于全局锁定判断) | ❌ 通常无后置镜像 | 用于加锁,不影响数据内容 |
以UPDATE的操作为例,说明一下源码中是如何生成前置和后置镜像的。
2.1.1、生成前置镜像
选择如图的实现:
首先通过 getTableMeta()
方法,获取先前缓存的数据库表元信息,包括表名,所有的字段名,索引等信息。
然后通过buildBeforeImageSQL
方法构建前置镜像:
最终生成一条类似于下面的SQL语句,加上FOR UPDATE相当于给查询出的结果加上了行锁,避免其他线程并发修改。
SELECT col1, col2, col3 FROM table_name WHERE ... ORDER BY ... LIMIT ... FOR UPDATE
最后执行buildTableRecords
方法,执行传入的SELECT ... FOR UPDATE
语句,并将查询结果(即将被修改的记录)封装为 Seata 内部统一格式TableRecords
对象,用于生成 Undo Log。
2.1.2、生成后置镜像
buildAfterImageSQL
方法,是在UPDATE执行之后,根据主键把刚刚修改的数据查询出,作为后置镜像。类似于如下的SQL:
SELECT col1, col2 FROM table_name WHERE (id = ?) OR (id = ?)
在buildRecords
方法中同样将查询结果(即将被修改的记录)封装为 Seata 内部统一格式TableRecords
对象,用于生成 Undo Log。
2.1.3、生成undolog
生成undolog
前,会进行判断,如果前置镜像和后置镜像都为空,那就没有必要生成undolog了。同样地,在update的场景下,如果前置镜像的行数和后置镜像的行数不相同,说明是有问题的,可能存在并发冲突。
这里只是对undolog进行的缓存,并没有写到数据库
总结
Seata 通过对客户端数据源进行代理增强,实现了在 SQL 执行前后生成前置镜像(Before Image)和后置镜像(After Image),并构建 UndoLog,以支持分布式事务的自动回滚能力。同时,在本地事务执行过程中会与 TC(Transaction Coordinator)协作,完成分支事务的注册和资源锁定。
在 Spring Boot 应用启动阶段,Seata 利用自动配置机制向 Spring 容器中注册了一个 SeataAutoDataSourceProxyCreator
类型的 Bean。该类继承自 AbstractAutoProxyCreator
,重写了 wrapIfNecessary
方法。在该方法中通过调用 buildProxy
,将业务数据源封装为 DataSourceProxy
实例,从而实现数据源的代理。
DataSourceProxy
的核心方法 getConnection()
返回的是一个增强后的连接对象 ConnectionProxy
。ConnectionProxy
继承自 AbstractConnectionProxy
,其重写的 prepareStatement()
方法会返回一个 PreparedStatementProxy
实例。在执行业务 SQL 时,实际调用的是其 execute()
方法,Seata 就是在这里织入了对事务的控制逻辑。
具体而言,execute()
方法的执行过程如下:
- 绑定全局事务 XID 到当前数据库连接;
- 设置连接为非自动提交;
- 根据 SQL 类型解析器获取增强执行器(如
UpdateExecutor
、InsertExecutor
等); - 构建并执行前置镜像查询(Before Image);
- 执行目标 SQL;
- 构建后置镜像查询(After Image);
- 基于镜像差异构建 UndoLog,并写入
undo_log
表; - 提交本地事务;
- 向 TC 注册分支事务和对应的资源锁信息。
通过这一整套机制,Seata 实现了对数据库操作的透明增强,使业务开发者无需关注底层事务控制细节,即可实现强一致性的分布式事务管理。