自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

61 張圖,剖析 Spring 事務(wù),就是要鉆到底!

開發(fā) 架構(gòu)
為了方便大家能更好看懂后面的源碼,我先整體介紹一下源碼的執(zhí)行流程,讓大家有一個整體的認識,否則容易被繞進去。

大家好,我是樓仔!

下面我會簡單介紹一下 Spring 事務(wù)的基礎(chǔ)知識,以及使用方法,然后直接對源碼進行拆解。

不 BB,上文章目錄。

圖片

1. 項目準備

需要搭建環(huán)境的同學(xué),代碼詳見:https://github.com/lml200701158/program_demo/tree/main/spring-transaction

下面是 DB 數(shù)據(jù)和 DB 操作接口:

uid

uname

usex

1

張三

2

陳恒

3

樓仔

// 提供的接口
public interface UserDao {
// select * from user_test where uid = "#{uid}"
public MyUser selectUserById(Integer uid);
// update user_test set uname =#{uname},usex = #{usex} where uid = #{uid}
public int updateUser(MyUser user);
}

基礎(chǔ)測試代碼,testSuccess() 是事務(wù)生效的情況:

@Service
public class Louzai {
@Autowired
private UserDao userDao;

public void update(Integer id){
MyUser user = new MyUser();
user.setUid(id);
user.setUname("張三-testing");
user.setUsex("女");
userDao.updateUser(user);
}

public MyUser query(Integer id){
MyUser user = userDao.selectUserById(id);
return user;
}

// 正常情況
@Transactional(rollbackFor = Exception.class)
public void testSuccess() throws Exception {
Integer id = 1;
MyUser user = query(id);
System.out.println("原記錄:" + user);
update(id);
throw new Exception("事務(wù)生效");
}
}

執(zhí)行入口:

public class SpringMyBatisTest {
public static void main(String[] args) throws Exception {
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
Louzai uc = (Louzai) applicationContext.getBean("louzai");
uc.testSuccess();
}
}

輸出:

16:44:38.267 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
16:44:38.363 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'txManager'
16:44:40.966 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.mybatis.controller.Louzai.testSuccess]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception
16:44:40.968 [main] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/java_study?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimeznotallow=Asia/Shanghai]
16:44:41.228 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [com.mysql.cj.jdbc.ConnectionImpl@5b5caf08] for JDBC transaction
16:44:41.231 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5b5caf08] to manual commit
原記錄:MyUser(uid=1, uname=張三, usex=)
16:42:59.345 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
16:42:59.346 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@70807224]
16:42:59.354 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@70807224] after transaction
Exception in thread "main" java.lang.Exception: 事務(wù)生效
at com.mybatis.controller.Louzai.testSuccess(Louzai.java:34)
// 異常日志省略...

2. Spring 事務(wù)工作流程

為了方便大家能更好看懂后面的源碼,我先整體介紹一下源碼的執(zhí)行流程,讓大家有一個整體的認識,否則容易被繞進去。

整個 Spring 事務(wù)源碼,其實分為 2 塊,我們會結(jié)合上面的示例,給大家進行講解。圖片

第一塊是后置處理,我們在創(chuàng)建 Louzai Bean 的后置處理器中,里面會做兩件事情:

獲取 Louzai 的切面方法:首先會拿到所有的切面信息,和 Louzai 的所有方法進行匹配,然后找到 Louzai 所有需要進行事務(wù)處理的方法,匹配成功的方法,還需要將事務(wù)屬性保存到緩存 attributeCache 中。

創(chuàng)建 AOP 代理對象:結(jié)合 Louzai 需要進行 AOP 的方法,選擇 Cglib 或 JDK,創(chuàng)建 AOP 代理對象。

圖片

第二塊是事務(wù)執(zhí)行,整個邏輯比較復(fù)雜,我只選取 4 塊最核心的邏輯,分別為從緩存拿到事務(wù)屬性、創(chuàng)建并開啟事務(wù)、執(zhí)行業(yè)務(wù)邏輯、提交或者回滾事務(wù)。

3. 源碼解讀

注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不一樣?。?!

上面的知識都不難,下面才是我們的重頭戲,讓你跟著樓仔,走一遍代碼流程。

3.1 代碼入口

圖片

圖片

這里需要多跑幾次,把前面的 beanName 跳過去,只看 louzai。

圖片

圖片

進入 doGetBean(),進入創(chuàng)建 Bean 的邏輯。

圖片

進入 createBean(),調(diào)用 doCreateBean()。

圖片

進入 doCreateBean(),調(diào)用 initializeBean()。

圖片

圖片

圖片

圖片

如果看過我前面幾期系列源碼的同學(xué),對這個入口應(yīng)該會非常熟悉,其實就是用來創(chuàng)建代理對象。

3.2 創(chuàng)建代理對象

圖片

這里是重點!敲黑板!??!

先獲取 louzai 類的所有切面列表;

創(chuàng)建一個 AOP 的代理對象。

圖片

3.2.1 獲取切面列表

圖片

這里有 2 個重要的方法,先執(zhí)行 findCandidateAdvisors(),待會我們還會再返回 findEligibleAdvisors()。

圖片

圖片

圖片

圖片

依次返回,重新來到 findEligibleAdvisors()。

圖片

圖片

圖片

圖片

進入 canApply(),開始匹配 louzai 的切面。

圖片

這里是重點!敲黑板!?。?/p>

這里只會匹配到 Louzai.testSuccess() 方法,我們直接進入匹配邏輯。

圖片

如果匹配成功,還會把事務(wù)的屬性配置信息放入 attributeCache 緩存。

圖片

圖片

圖片

圖片

圖片

圖片

我們依次返回到 getTransactionAttribute(),再看看放入緩存中的數(shù)據(jù)。

圖片

再回到該小節(jié)開頭,我們拿到 louzai 的切面信息,去創(chuàng)建 AOP 代理對象。

圖片

3.2.2 創(chuàng)建 AOP 代理對象

創(chuàng)建 AOP 代理對象的邏輯,在上一篇文章(Spring AOP)講解過,我是通過 Cglib 創(chuàng)建,感興趣的同學(xué)可以關(guān)注公眾號「樓仔」,翻一下樓仔的歷史文章。

3.3 事務(wù)執(zhí)行

回到業(yè)務(wù)邏輯,通過 louzai 的 AOP 代理對象,開始執(zhí)行主方法。

圖片

因為代理對象是 Cglib 方式創(chuàng)建,所以通過 Cglib 來執(zhí)行。

圖片

圖片

圖片

圖片

這里是重點!敲黑板!?。?/p>

下面的代碼是事務(wù)執(zhí)行的核心邏輯 invokeWithinTransaction()。

圖片

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {

//獲取我們的事務(wù)屬源對象
TransactionAttributeSource tas = getTransactionAttributeSource();
//通過事務(wù)屬性源對象獲取到我們的事務(wù)屬性信息
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//獲取我們配置的事務(wù)管理器對象
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//從tx屬性對象中獲取出標注了@Transactionl的方法描述符
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

//處理聲明式事務(wù)
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//有沒有必要創(chuàng)建事務(wù)
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

Object retVal;
try {
//調(diào)用鉤子函數(shù)進行回調(diào)目標方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//拋出異常進行回滾處理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清空我們的線程變量中transactionInfo的值
cleanupTransactionInfo(txInfo);
}
//提交事務(wù)
commitTransactionAfterReturning(txInfo);
return retVal;
}
//編程式事務(wù)
else {
// 這里不是我們的重點,省略...
}
}

3.3.1 獲取事務(wù)屬性

在 invokeWithinTransaction() 中,我們找到獲取事務(wù)屬性的入口。

圖片

從 attributeCache 獲取事務(wù)的緩存數(shù)據(jù),緩存數(shù)據(jù)是在 “2.2.1 獲取切面列表” 中保存的。

圖片

3.3.2 創(chuàng)建事務(wù)

圖片

圖片

圖片

通過 doGetTransaction() 獲取事務(wù)。

protected Object doGetTransaction(){
//創(chuàng)建一個數(shù)據(jù)源事務(wù)對象
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//是否允許當前事務(wù)設(shè)置保持點
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/**
* TransactionSynchronizationManager 事務(wù)同步管理器對象(該類中都是局部線程變量)
* 用來保存當前事務(wù)的信息,我們第一次從這里去線程變量中獲取 事務(wù)連接持有器對象 通過數(shù)據(jù)源為key去獲取
* 由于第一次進來開始事務(wù) 我們的事務(wù)同步管理器中沒有被存放.所以此時獲取出來的conHolder為null
*/
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
//返回事務(wù)對象
return txObject;
}

通過 startTransaction() 開啟事務(wù)。

圖片

下面是開啟事務(wù)的詳細邏輯,了解一下即可。

protected void doBegin(Object transaction, TransactionDefinition definition){
//強制轉(zhuǎn)化事務(wù)對象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;

try {
//判斷事務(wù)對象沒有數(shù)據(jù)庫連接持有器
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//通過數(shù)據(jù)源獲取一個數(shù)據(jù)庫連接對象
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//把我們的數(shù)據(jù)庫連接包裝成一個ConnectionHolder對象 然后設(shè)置到我們的txObject對象中去
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}

//標記當前的連接是一個同步事務(wù)
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();

//為當前的事務(wù)設(shè)置隔離級別
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);

//關(guān)閉自動提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}

//判斷事務(wù)為只讀事務(wù)
prepareTransactionalConnection(con, definition);
//設(shè)置事務(wù)激活
txObject.getConnectionHolder().setTransactionActive(true);

//設(shè)置事務(wù)超時時間
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}

// 綁定我們的數(shù)據(jù)源和連接到我們的同步管理器上 把數(shù)據(jù)源作為key,數(shù)據(jù)庫連接作為value 設(shè)置到線程變量中
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}

catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//釋放數(shù)據(jù)庫連接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}

最后返回到 invokeWithinTransaction(),得到 txInfo 對象。

圖片

3.3.3 執(zhí)行邏輯

還是在 invokeWithinTransaction() 中,開始執(zhí)行業(yè)務(wù)邏輯。

圖片

圖片

圖片

圖片

圖片

進入到真正的業(yè)務(wù)邏輯。

圖片

執(zhí)行完畢后拋出異常,依次返回,走后續(xù)的回滾事務(wù)邏輯。

3.3.4 回滾事務(wù)

還是在 invokeWithinTransaction() 中,進入回滾事務(wù)的邏輯。

圖片

。

圖片

執(zhí)行回滾邏輯很簡單,我們只看如何判斷是否回滾。

圖片

圖片

圖片

如果拋出的異常類型,和事務(wù)定義的異常類型匹配,證明該異常需要捕獲。

之所以用遞歸,不僅需要判斷拋出異常的本身,還需要判斷它繼承的父類異常,滿足任意一個即可捕獲。

圖片

到這里,所有的流程結(jié)束。

4. 結(jié)語

我們再小節(jié)一下,文章先介紹了事務(wù)的使用示例,以及事務(wù)的執(zhí)行流程。

之后再剖析了事務(wù)的源碼,分為 2 塊:

先匹配出 louzai 對象所有關(guān)于事務(wù)的切面列表,并將匹配成功的事務(wù)屬性保存到緩存;

從緩存取出事務(wù)屬性,然后創(chuàng)建、啟動事務(wù),執(zhí)行業(yè)務(wù)邏輯,最后提交或者回滾事務(wù)。

責任編輯:武曉燕 來源: 樓仔
相關(guān)推薦

2023-09-28 21:37:41

HashMap多線程

2023-09-11 22:19:38

Spring啟動技術(shù)

2014-08-25 09:12:47

Spring事務(wù)管理

2010-03-29 13:34:15

ibmdwSpring

2010-03-23 08:46:40

Spring

2023-10-10 08:16:07

Spring依賴注入SpEL表達式

2021-04-21 12:29:45

KafkaZookeeper模型

2023-10-12 08:54:20

Spring事務(wù)設(shè)置

2017-04-06 10:11:26

金蝶

2022-07-04 11:06:02

RocketMQ事務(wù)消息實現(xiàn)

2020-05-06 09:10:46

AQS同步器CAS

2020-11-30 14:40:52

事務(wù)系統(tǒng)項目

2022-10-11 08:27:45

Spring事務(wù)管理性能統(tǒng)計

2009-09-08 16:20:12

LINQ to SQL

2010-03-02 13:43:01

WCF事務(wù)演示

2018-10-17 18:46:53

數(shù)字銀行創(chuàng)業(yè)公司用戶

2020-02-10 19:34:12

生命周期流程流程圖

2010-06-12 14:35:46

UML對象圖

2010-06-29 11:00:25

UML類圖實例

2009-09-23 17:48:00

Hibernate事務(wù)
點贊
收藏

51CTO技術(shù)棧公眾號