文章有點長,我決定用半個小時來和你分享~?? 廢話不多說,上代碼,,,

基于Seata 1.5.2,專案中用 seata-spring-boot-starter
1. SeataDataSourceAutoConfiguration

SeataDataSourceAutoConfiguration 主要是配置資料源代理,可以看到:
- 默認seata.enabled、seata.enableAutoDataSourceProxy、seata.enable-auto-data-source-proxy都是true
- 只有當classpath中有DataSource時才會進行此配置
- 創建了一個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端的原始碼學習就先到這里,歡迎交流~
如果你都已經看到了這里,不妨給我點個贊吧??
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/530538.html
標籤:其他
