文章有点长,我决定用半个小时来给您分享~

基于Seata 1.5.2,项目中用 seata-spring-boot-starter

1. SeataDataSourceAutoConfiguration

SeataDataSourceAutoConfiguration 主要是配置数据源代理,可以看到:

  1. 默认seata.enabled、seata.enableAutoDataSourceProxy、seata.enable-auto-data-source-proxy都是true
  2. 只有当classpath中有DataSource时才会进行此配置
  3. 创建了一个SeataAutoDataSourceProxyCreator,用于自动代理数据源

先记一下,SeataAutoDataSourceProxyCreator是一个BeanPostProcessor

刚才new了一个SeataAutoDataSourceProxyCreator,继续看构造方法,默认useJdkProxy是false,excludes为空,dataSourceProxyMode是AT

构造方法中最重要的一件事情是构造AOP通知(拦截器),这里new了一个SeataAutoDataSourceProxyAdvice

SeataAutoDataSourceProxyAdvice是一个MethodInterceptor。

MethodInterceptor是aop中的一个接口,当目标方法被调用时就会调用与之关联的MethodInterceptor的invoke方法

至此,在构造方法中完成了advisors的赋值,advisors[]中有一个DefaultIntroductionAdvisor,DefaultIntroductionAdvisor中引用了SeataAutoDataSourceProxyAdvice

前面说过,SeataAutoDataSourceProxyCreator是一个BeanPostProcessor,而BeanPostProcessor是BeanFactory中的一个钩子(回调),称之为后置处理器

AbstractAutoProxyCreator#postProcessBeforeInstantiation()

AbstractAutoProxyCreator#postProcessAfterInitialization()

AbstractAutoProxyCreator#wrapIfNecessary()

AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean()

SeataAutoDataSourceProxyCreator#getAdvicesAndAdvisorsForBean()

SeataAutoDataSourceProxyCreator#wrapIfNecessary()

DataSourceProxyHolder维护了数据源对象与数据源代理对象的映射

至此,数据源代理部分就看完了,下面总结一下:

1、启动的时候自动配置数据源代理,创建了一个SeataAutoDataSourceProxyCreator

2、SeataAutoDataSourceProxyCreator在构造方法中创建AOP通知,并赋值给其属性

3、AbstractAutoProxyCreator是一个抽象类不能被实例化,能实例化的只有SeataAutoDataSourceProxyCreator

4、SeataAutoDataSourceProxyCreator从AbstractAutoProxyCreator那里继承了很多属性和方法,其中就包括postProcessBeforeInstantiation()、postProcessAfterInitialization()、createProxy()等等

5、SeataAutoDataSourceProxyCreator间接实现了BeanPostProcessor接口,也就是说它也是BeanPostProcessor的一个实现类

6、BeanFactory回调所有的BeanPostProcessor#postProcessAfterInitialization()时,就会调用SeataAutoDataSourceProxyCreator的postProcessAfterInitialization()方法,最终会调用wrapIfNecessary()方法

7、wrapIfNecessary()只关心DataSource对象,它负责为DataSource对象生成代理对象,并且在SeataAutoDataSourceProxyCreator中维护了DataSource对象与SeataDataSourceProxy对象之间的映射关心

8、创建代理对象时,会给DataSource对象应用AOP拦截器。用AOP的话来讲,就是给目标对象DataSource织入通知,并创建一个被增强的代理对象

9、通知(拦截器)是SeataAutoDataSourceProxyAdvice,它实现了MethodInterceptor接口

10、SeataAutoDataSourceProxyAdvice#invoke()方法所做的事情就是,拿到原始DataSource的代理对象,并且在代理对象上调用目标方法

综上所述,以上做的所有工作都是为了将来调用 javax.sql.DataSource 上的任意方法时都会被拦截,然后调用其代理对象上对应的方法。而DataSource中最重要的一个方法就是getConnection()

划重点:将来,所有调用 javax.sql.DataSource#getConnection() 都会被拦截,然后在代理对象上执行getConnection(),因此可以这样说

调 javax.sql.DataSource#getConnection() 实际上执行的是 io.seata.rm.datasource.SeataDataSourceProxy#getConnection()

2. SeataAutoConfiguration

SeataAutoConfiguration里面主要是配置GlobalTransactionScanner(全局事务扫描器)

seata.enabled=true 才会开启 SeataAutoConfiguration

GlobalTransactionScanner 也继承自 AbstractAutoProxyCreator,同时还实现了InitializingBean接口。BeanFactory在设置了所有bean属性之后会调用InitializingBean的afterPropertiesSet()方法

GlobalTransactionScanner#afterPropertiesSet()

io.seata.common.DefaultValues中定义了很多默认值

同样地,因为实现了BeanPostProcessor接口,所以在启动时BeanFactory实例化Bean之后,会调用GlobalTransactionScanner的postProcessAfterInitialization(),尽管这个postProcessAfterInitialization()方法时从AbstractAutoProxyCreator那里继承来的,但是不影响啊,还是会调用GlobalTransactionScanner这个bean的postProcessAfterInitialization()方法。于是,最终又会调wrapIfNecessary()方法。

GlobalTransactionScanner#wrapIfNecessary()

这里面有一个很重要的逻辑就是,创建了一个GlobalTransactionalInterceptor对象,并赋值给interceptor

AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean()是一个抽象方法,实现在子类GlobalTransactionScanner中

因此,所有在GlobalTransactionScanner#wrapIfNecessary()中被代理的对象,都被应用GlobalTransactionalInterceptor

GlobalTransactionalInterceptor也是一个MethodInterceptor

也就是说,目标方法的调用都会转到GlobalTransactionalInterceptor#invoke()上

GlobalTransactionalInterceptor#handleGlobalTransaction()

事务执行直接调用TransactionalTemplate的execute()方法

io.seata.tm.api.TransactionalTemplate#execute()

io.seata.tm.api.GlobalTransactionContext#getCurrent() 获取当前事务

io.seata.tm.api.TransactionalTemplate#beginTransaction()

tx是DefaultGlobalTransaction

io.seata.tm.api.DefaultGlobalTransaction#begin()

DefaultGlobalTransaction中的TransactionManager是DefaultTransactionManager

DefaultTransactionManager中提供了事务相关的底层操作

io.seata.tm.api.DefaultGlobalTransaction#commit()

io.seata.tm.api.DefaultGlobalTransaction#rollback()的逻辑与commit()类似,都是重试调用transactionManager.rollback(xid)

全局事务扫描器部分的代码就看到这里,下面总结一下:

1、配置项seata.enabled=true 会触发 SeataAutoConfiguration 自动配置

2、SeataAutoConfiguration中创建了一个GlobalTransactionScanner

3、GlobalTransactionScanner继承了AbstractAutoProxyCreator,并实现InitializingBean接口

4、初始化TM、RM

5、由于继承了AbstractAutoProxyCreator,所以BeanFactory会调用GlobalTransactionScanner#方法postProcessAfterInitialization(),最终会调用GlobalTransactionScanner#wrapIfNecessary()来为目标对象创建代理对象

6、GlobalTransactionScanner#wrapIfNecessary()中创建了一个GlobalTransactionalInterceptor,GlobalTransactionalInterceptor是一个MethodInterceptor

7、在创建代理对象的时候,在AbstractAutoProxyCreator#wrapIfNecessary()方法中,为代理对象应用GlobalTransactionalInterceptor,于是所有目标对象上的方法调用就会转为调用GlobalTransactionalInterceptor#invoke()

8、GlobalTransactionalInterceptor#invoke()方法中,首先获取被调用的目标对象的Class和Method对象,然后检查目标方法或类上是否有@GlobalTransactional或@GlobalLock注解,而且配置项中不能禁用全局事务

9、如果加了@GlobalTransactional注解,则创建一个AspectTransactional,然后开始处理全局事务,默认传播特性是REQUIRED

10、如果加了@GlobalLock注解,则开始处理全局锁

11、处理全局事务就是直接调用事务模板中的execute方法,TransactionalTemplate#execute()是一个模板方法,其中定义了事务处理的流程。首先开启事务,然后执行业务逻辑,最后提交事务,异常回滚事务。

12、事务操作是在DefaultGlobalTransaction中处理的,最终处理在DefaultTransactionManager。DefaultTransactionManager负责同步远程调用,向TC发请求来开启、提交、回滚事务等操作

3. 数据库操作执行SQL语句

通过Java自带的JDBC操作数据库通常是这样的:

Class.forName(driverClass);
// 获取Connection
Connection connection = DriverManager.getConnection(url,user,password);
// 创建Statement或者PreparedStatement
Statement stmt = connection.createStatement();
stmt.execute(sql); // PreparedStatement ps = connection.prepareStatement(sql);
// ps.execute();

MyBatis底层也是这一套

接下来看Seata是如何做的

首先是获取数据库连接Connection,前面已经说过了,调用DataSource的getConnection()方法底层是在代理对象SeataDataSourceProxy上调用getConnection()。SeataDataSourceProxy是接口,如果是AT模式,则这个数据源代理对象是DataSourceProxy

DataSourceProxy#getConnection()获取数据库连接

ConnectionProxy#createStatement()

ConnectionProxy#prepareStatement()

PreparedStatementProxy 继承自 StatementProxy,因此下面就直接看PreparedStatementProxy如何执行SQL

PreparedStatementProxy#execute()

ExecuteTemplate#execute()  是一个模板方法

挑一个看看吧,就挑UpdateExecutor

UpdateExecutor构造方法中一直调父类的构造法,既然如此,那么直接看BaseTransactionalExecutor

UpdateExecutor#execute()

这个方法时从BaseTransactionalExecutor那里继承来的,又是一个模板方法,可见设计模式是多么重要

AbstractDMLBaseExecutor#doExecute()

AbstractDMLBaseExecutor#executeAutoCommitTrue()

ConnectionProxy#changeAutoCommit()

现在事务自动提交已经被Seata改成false了

UpdateExecutor#beforeImage()

BaseTransactionalExecutor#prepareUndoLog()

接下来,提交事务

ConnectionProxy#commit()

ConnectionProxy#processGlobalTransactionCommit() 处理全局事务提交

分支事务提交以后,业务数据更改和undo_log就都提交了

回想一下,为什么在执行业务修改前要先将默认的自动提交改成手动提交,最后再改成自动提交呢?

因为,要将业务数据修改和插入undo_log放在同一个事务里,一起提交

这一切都归功于代理

回顾一下整个调用链

结合之前的案例,AT模式TC、TM、RM三者的交互应该是这样的:

问题一:为什么在执行的时候,先将数据库自动提交autoCommit设为false,最后再改成true呢?

答:因为,需要将undo_log和业务数据修改放到同一个事务中,这样可以保证业务数据修改成功后undo_log必然插入成功,所以Seata要将其改为手动提交。最后再改成true是因为默认autoCommit就是true,这样可以不影响其它业务。

问题二:什么情况下ConnectionContext中xid=null,且isGlobalLockRequire=true呢?或者换一种问法,什么情况下不在全局事务中,当仍然需要全局锁呢?

答:当业务方法上不加@GlobalTransactional,而是只加了@GlobalLock注解的情况下,就会出现上述情况,也就会执行 ConnectionProxy#processLocalCommitWithGlobalLocks()方法,在事务提交前检查全局锁,这样设计的目的是在AT模式下,不出现脏读、脏写。由于数据源被代理了,当一个加了@GlobalTransactional的全局事务,与另一个加了@GlobalTransactional或@GlobalLock注解的事务在本地事务提交前就会检查全局锁,要先获得全局锁才能提交本地事务,这样就避免了脏读脏写,从而相当于实现了全局事务的读已提交隔离级别。参见:https://seata.io/zh-cn/blog/seata-at-lock.html

关于Seata 1.5.2 Client端的源码学习就先到这里,欢迎交流~

如果你都已经看到了这里,不妨给我点个赞吧

Seata 1.5.2 源码学习的更多相关文章

  1. Seata Server 1.5.2 源码学习

    Seata 包括 Server端和Client端.Seata中有三种角色:TC.TM.RM,其中,Server端就是TC,TM和RM属Client端.Client端的源码学习上一篇已讲过,详见 < ...

  2. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  3. jQuery源码学习感想

    还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  8. 我的angularjs源码学习之旅2——依赖注入

    依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...

  9. ddms(基于 Express 的表单管理系统)源码学习

    ddms是基于express的一个表单管理系统,今天抽时间看了下它的代码,其实算不上源码学习,只是对它其中一些小的开发技巧做一些记录,希望以后在项目开发中能够实践下. 数据层封装 模块只对外暴露mod ...

随机推荐

  1. 二叉搜索树TREE(线段树,区间DP)

    前言 线段树+区间DP题,线段树却不是优化DP的,是不是很意外? 题面 二叉搜索树是一种二叉树,每个节点都有一个权值,并且一个点的权值比其左子树里的点权值都大,比起右子树里的点权值都小. 一种朴素的向 ...

  2. 2019 CSP-S Ⅱ 游记

    day0(试机) 第零天,重新打了一遍头文件和读优,熟悉了一下就匆匆走了. day1 T1一看到先把二分打了,然后发现long long要爆,好慌 主要是基础知识不够扎实,不知道unsigned lo ...

  3. 「题解报告」P7301 【[USACO21JAN] Spaced Out S】

    原题传送门 神奇的5分算法:直接输出样例. 20分算法 直接把每个点是否有牛的状态DFS一遍同时判断是否合法,时间复杂度约为\(O(2^{n^2})\)(因为有判断合法的剪枝所以会比这个低).而在前四 ...

  4. Java循环解析

    Java循环解析 while循环(先判断) int i=0; while (i<100){    i++;    System.out.println(i); } DoWhile循环(先执行,后 ...

  5. iOS 苹果集成登录及苹果图标的制作要求

    前言 如果要上架的应用集成了三方登录,那么在审核时,苹果会强制要求应用也要集成苹果登录.如果应用没有集成一般情况下都会被审核团队给打回来. 苹果集成登录 首先,你需要在开发者中心,找到你的应用,勾选上 ...

  6. 如何在 Windows 和 Linux 上确定系统使用的是 MBR 分区还是 GPT 分区详细步骤!!!

    在 Windows 上检查系统使用的是 MBR 分区还是 GPT 分区 点击放大镜搜索输入disk 点击打开 进入之后,右键点击你想要检查分区方案的磁盘,在右键菜单里选择属性! 在属性窗口,切换到卷, ...

  7. Python数据科学手册-Pandas:数值运算方法

    Numpy 的基本能力之一是快速对每个元素进行运算 Pandas 继承了Numpy的功能,也实现了一些高效技巧. 对于1元运算,(函数,三角函数)保留索引和列标签 对于2元运算,(加法,乘法),Pan ...

  8. MySQL 的七种日志总结

    文章转载自:https://mp.weixin.qq.com/s/ewv7HskHvH3O7kFyOmoqgw 一.MySQL 日志分类 日志类别 说明 备注 错误日志 错误日志记录了当MySQL启动 ...

  9. 5.监控MySQL

    prometheus监控MySQL需要用到mysql_exporter. mysql_exporter 项目地址:https://github.com/prometheus/mysqld_export ...

  10. Portainer安装,配置自定义镜像仓库拉取镜像

    Portainer介绍 Portainer是Docker的图形化管理工具,提供状态显示面板.应用模板快速部署.容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作).事件日志显示.容器控制台 ...