中英文網(wǎng)站asp怎么做seo推廣軟件費(fèi)用
當(dāng)構(gòu)建復(fù)雜的企業(yè)級(jí)應(yīng)用程序時(shí),數(shù)據(jù)一致性和可靠性是至關(guān)重要的。Spring 框架提供了強(qiáng)大而靈活的事務(wù)管理機(jī)制,成為開(kāi)發(fā)者處理事務(wù)的首選工具。本文將深入探討 Spring 事務(wù)的使用和原理,為大家提供全面的了解和實(shí)際應(yīng)用的指導(dǎo)。
本文概覽
- 首先,我們將從事務(wù)的基礎(chǔ)出發(fā),介紹其概念、生命周期、隔離級(jí)別、傳播行為。
- 其次,我們?cè)俳榻B在 Spring 中,如何應(yīng)用聲明式和編程式兩種事務(wù)管理方式。
- 最后,我們將深入研究 Spring 事務(wù)的原理,了解其核心組件和關(guān)鍵類(lèi),解析其工作原理,探索它是如何做到將事務(wù)的控制與業(yè)務(wù)邏輯進(jìn)行解耦的。
事務(wù)基礎(chǔ)
事務(wù)簡(jiǎn)介
在數(shù)據(jù)庫(kù)和軟件開(kāi)發(fā)領(lǐng)域,事務(wù)是一組相關(guān)的操作
,被視為不可分割的執(zhí)行單位。事務(wù)具有四個(gè)關(guān)鍵數(shù)據(jù),簡(jiǎn)稱(chēng) ACID
屬性:
- 原子性(Atomicity):事務(wù)是原子的,它要么全部執(zhí)行成功,要么完全不執(zhí)行。如果事務(wù)的任何部分失敗,整個(gè)事務(wù)將回滾到初始狀態(tài),不會(huì)留下部分完成的結(jié)果。
- 一致性(Consistency):事務(wù)在執(zhí)行前后,數(shù)據(jù)庫(kù)的狀態(tài)應(yīng)保持一致。這意味著事務(wù)的執(zhí)行不會(huì)破壞數(shù)據(jù)庫(kù)的完整性約束,如唯一性約束、外鍵約束等。
- 隔離性(Isolation):多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),每個(gè)事務(wù)都應(yīng)該被隔離,以防止彼此之間的干擾。數(shù)據(jù)庫(kù)系統(tǒng)通過(guò)事務(wù)隔離級(jí)別來(lái)定義事務(wù)之間的隔離程度。
- 持久性(Durability):一旦事務(wù)成功完成,其結(jié)果應(yīng)該是持久的,即使在系統(tǒng)故障或重啟后也應(yīng)該保持。數(shù)據(jù)庫(kù)系統(tǒng)通常通過(guò)將事務(wù)的結(jié)果寫(xiě)入日志文件來(lái)實(shí)現(xiàn)持久性。
事務(wù)的生命周期通常包括一下階段:
- 開(kāi)始:事務(wù)開(kāi)始時(shí),系統(tǒng)記錄數(shù)據(jù)庫(kù)的初始狀態(tài)。
- 執(zhí)行:事務(wù)執(zhí)行相關(guān)的數(shù)據(jù)庫(kù)操作,可能包括插入、更新、刪除等。
- 提交:如果事務(wù)成功執(zhí)行,將對(duì)數(shù)據(jù)庫(kù)的更改提交,使其成為永久性的。
- 回滾:如果在事務(wù)執(zhí)行期間發(fā)生錯(cuò)誤或者事務(wù)被顯示混滾,系統(tǒng)將撤銷(xiāo)事務(wù)中的所有更改,回復(fù)數(shù)據(jù)庫(kù)到事務(wù)開(kāi)始時(shí)的狀態(tài)。
下面我們通過(guò)一些例子來(lái)深入理解下事務(wù)的生命周期過(guò)程:
案例一:開(kāi)啟事務(wù)并插入一條數(shù)據(jù),執(zhí)行成功并提交事務(wù)
-- 開(kāi)始事務(wù)
BEGIN;
-- 執(zhí)行數(shù)據(jù)庫(kù)操作,向 `user` 表中插入一條數(shù)據(jù)
INSERT INTO `user` (name,age,address) VALUE ("帥氣的小張",25,"山東菏澤");
-- 提交事務(wù)
COMMIT;
案例二:開(kāi)啟事務(wù)插入兩條數(shù)據(jù),其中第二條數(shù)據(jù)執(zhí)行異常,事務(wù)發(fā)生回滾,那么第一條數(shù)據(jù)并沒(méi)有生效
-- 開(kāi)始事務(wù)
BEGIN;
-- 執(zhí)行操作,向 `user` 表中插入一條數(shù)據(jù)
INSERT INTO `user` (name,age,address) VALUE ("帥氣的小張",25,"山東菏澤");
-- 執(zhí)行一條異常操作 address 字段拼錯(cuò)
INSERT INTO `user` (name,age,adress) VALUE ("帥氣的小張",25,"山東菏澤");
-- 回滾事務(wù)
ROLLBACK;
可以看到,在 MySQL 里,執(zhí)行事務(wù)的操作包括 BEGIN(開(kāi)啟)、COMMIT(提交)、ROLLBACK(回滾)
案例三:使用 Spring 框架時(shí),進(jìn)行聲明式事務(wù)管理
package com.markus.spring.transaction.service;import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Objects;/*** @Author: zhangchenglong06* @Date: 2024/2/2* @Description:*/
@Service
public class UserService {@Autowiredprivate UserDao userDao;@Transactionalpublic void processUser() {User user = new User();user.setName("markus zhang unique time :" + System.currentTimeMillis());user.setAge(25);user.setAddress("山東菏澤");// 1. 先向數(shù)據(jù)庫(kù)中插入一條數(shù)據(jù)userDao.insertUser(user);// 故意拋出一個(gè)異常,驗(yàn)證下 第一步 的操作是否會(huì)回滾int i = 1 / 0;// 2. 再查詢(xún)?cè)摂?shù)據(jù)User queryUserByName = userDao.queryUserByName(user.getName());if (Objects.isNull(queryUserByName)) {return;}// 3. 再更新該數(shù)據(jù)到數(shù)據(jù)庫(kù)中queryUserByName.setAddress("北京朝陽(yáng)");userDao.updateUser(queryUserByName);}
}
案例四:使用 Spring 框架時(shí),進(jìn)行編程式事務(wù)管理
package com.markus.spring.transaction.service;import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;import java.util.List;
import java.util.Objects;/*** @Author: zhangchenglong06* @Date: 2024/2/2* @Description:*/
@Service
public class UserService {@Autowiredprivate UserDao userDao;@Autowiredprivate TransactionTemplate transactionTemplate;public void processUserByProgram() {User user = new User();user.setName("markus zhang unique time :" + System.currentTimeMillis());user.setAge(25);user.setAddress("山東菏澤");transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {try {processUser();} catch (Exception e) {// 如果發(fā)生異常,回滾事務(wù)status.setRollbackOnly();throw e;}}});}public void processUser() {User user = new User();user.setName("markus zhang unique time :" + System.currentTimeMillis());user.setAge(25);user.setAddress("山東菏澤");// 1. 先向數(shù)據(jù)庫(kù)中插入一條數(shù)據(jù)userDao.insertUser(user);// 故意拋出一個(gè)異常,驗(yàn)證下 第一步 的操作是否會(huì)回滾int i = 1 / 0;// 2. 再查詢(xún)?cè)摂?shù)據(jù)User queryUserByName = userDao.queryUserByName(user.getName());if (Objects.isNull(queryUserByName)) {return;}// 3. 再更新該數(shù)據(jù)到數(shù)據(jù)庫(kù)中queryUserByName.setAddress("北京朝陽(yáng)");userDao.updateUser(queryUserByName);}public List<User> queryAllUsers() {List<User> users = userDao.queryUsers(-1);return users;}
}
事務(wù)的隔離級(jí)別
上面在講隔離性
的時(shí)候提到數(shù)據(jù)庫(kù)通過(guò)事務(wù)隔離級(jí)別
來(lái)實(shí)現(xiàn)隔離性,那什么是事務(wù)隔離級(jí)別
呢?
它定義了多個(gè)事務(wù)之間相互影響的程度,以及它們能否同時(shí)運(yùn)行。在數(shù)據(jù)庫(kù)中,有四個(gè)標(biāo)準(zhǔn)的隔離級(jí)別,分別是讀未提交(Read Uncommitted)、讀已提交(Read Committed)、可重復(fù)讀(Repeatable Read)和串行化(Serializable)。
讀未提交(Read Uncommitted)
- 允許事務(wù)讀取其他事務(wù)未提交的數(shù)據(jù)
- 它是最低的事務(wù)隔離級(jí)別,存在
臟讀
問(wèn)題,即一個(gè)事務(wù)讀取到了另一個(gè)事務(wù)未提交的數(shù)據(jù)。
我們通過(guò)一個(gè)示例來(lái)理解一下:
# session one
-- 開(kāi)始事務(wù)
BEGIN;
-- 執(zhí)行數(shù)據(jù)庫(kù)操作,向 `user` 表中插入一條數(shù)據(jù)
INSERT INTO `user` (name,age,address) VALUE ("帥氣的小張",25,"山東菏澤");
-- 提交事務(wù)
COMMIT;# session two
SELECT * FROM `user`
按照下圖執(zhí)行順序可以看到,會(huì)話二的查詢(xún)操作執(zhí)行時(shí),可以讀到會(huì)話一還未提交的數(shù)據(jù)。
讀已提交(Read Committed)
- 保證一個(gè)事務(wù)提交后才被其他事務(wù)讀取。
- 解決了
臟讀
的問(wèn)題,但仍存在不可重復(fù)讀
問(wèn)題,即一個(gè)事務(wù)(A)在兩次讀取之間,另一個(gè)事務(wù)(B)修改了數(shù)據(jù),導(dǎo)致 A 兩次讀取的數(shù)據(jù)不一致。
我們先來(lái)看下 讀已提交
解決臟讀
的場(chǎng)景
可以看出,再次執(zhí)行事務(wù)一的插入操作后(未提交事務(wù)),事務(wù)二執(zhí)行查詢(xún)邏輯時(shí),并沒(méi)有查詢(xún)到數(shù)據(jù)。
我們?cè)賮?lái)看下產(chǎn)生不可重復(fù)讀
的問(wèn)題,也就是在同一事務(wù)中前后讀取的數(shù)據(jù)不一致。
可重復(fù)讀(Repeatable Read)
- 保證一個(gè)事務(wù)在其生命周期內(nèi)多次讀取同一數(shù)據(jù)時(shí),得到的結(jié)果是一致的。
- 解決了不可重復(fù)讀的問(wèn)題,但仍然可能存在
幻讀
問(wèn)題,即一個(gè)事務(wù)在兩次查詢(xún)之間,另一個(gè)事務(wù)插入了新的數(shù)據(jù)。
如下圖所示,可以看出,可重復(fù)讀
事務(wù)隔離級(jí)別可以解決同一事務(wù)中讀取到不同的數(shù)據(jù)問(wèn)題,但實(shí)際上,這可能也是一種虛假的數(shù)據(jù),也就是幻讀。
串行化(Serializable)
- 這是最高的事務(wù)隔離級(jí)別,確保事務(wù)之間不會(huì)發(fā)生臟讀、不可重復(fù)讀和幻讀。
- 通過(guò)對(duì)事務(wù)進(jìn)行串行化來(lái)避免并發(fā)問(wèn)題,但可能導(dǎo)致性能下降,因?yàn)樗枞似渌聞?wù)的并發(fā)執(zhí)行。
Spring 中的應(yīng)用
在 Spring 中,事務(wù)隔離級(jí)別通過(guò) Isolation 接口表示。
public enum Isolation {DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);private final int value;Isolation(int value) {this.value = value;}public int value() {return this.value;}}
在 Spring 中,事務(wù)隔離級(jí)別可以通過(guò)@Transactional
注解或者TransactionDefinition
接口進(jìn)行設(shè)置。例如:
通過(guò)注解:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void someTransactionalMethod() {// 事務(wù)處理邏輯
}
通過(guò)編程:
public void processUserByProgram() {User user = new User();user.setName("markus zhang unique time :" + System.currentTimeMillis());user.setAge(25);user.setAddress("山東菏澤");// 設(shè)置 事務(wù)隔離級(jí)別transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);// 執(zhí)行 事務(wù)transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {try {processUser();} catch (Exception e) {// 如果發(fā)生異常,回滾事務(wù)status.setRollbackOnly();throw e;}}});}
事務(wù)的傳播行為
事務(wù)的傳播行為定義了當(dāng)一個(gè)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),他們之間的交互方式,以及新事務(wù)如何與已有事務(wù)進(jìn)行關(guān)聯(lián)。Spring 框架引入了事務(wù)傳播行為的概念,并提供了靈活的事務(wù)管理機(jī)制,使得開(kāi)發(fā)者可以根據(jù)具體需求配置事務(wù)的傳播行為??梢酝ㄟ^(guò)@Transactional
注解或者編程式事務(wù)管理,Spring 允許開(kāi)發(fā)這選擇合適的傳播行為,以適應(yīng)各種業(yè)務(wù)場(chǎng)景。下面我們來(lái)學(xué)習(xí)下幾種常見(jiàn)的事務(wù)傳播行為。
REQUIRED(默認(rèn)值)
- 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒(méi)有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
- 這是最常見(jiàn)的傳播行為,適用于大多數(shù)情況。
@Transactional(propagation = Propagation.REQUIRED)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
REQUIRED_NEW
- 總是創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù),則將其掛起。
- 適用于需要獨(dú)立事務(wù)運(yùn)行的情況。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
SUPPORTS
- 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒(méi)有事務(wù),則以非事務(wù)的方式執(zhí)行。
- 適用于不需要事務(wù)支持的場(chǎng)景。
@Transactional(propagation = Propagation.SUPPORTS)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
NOT_SUPPORTED
- 以非事務(wù)的方式執(zhí)行,如果當(dāng)前存在事務(wù),則將其掛起。
- 適用于不希望在事務(wù)中執(zhí)行的場(chǎng)景。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
NEVER
- 以非事務(wù)的方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
- 適用于不希望在事務(wù)中執(zhí)行,則確保不會(huì)存在事務(wù)的場(chǎng)景。
@Transactional(propagation = Propagation.NEVER)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
MANDATORY
- 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒(méi)有事務(wù),則拋出異常。
- 適用于要求在已存在的事務(wù)中運(yùn)行的場(chǎng)景。
@Transactional(propagation = Propagation.MANDATORY)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
NESTED
- 如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)嵌套事務(wù),它是當(dāng)前事務(wù)的子事務(wù);如果當(dāng)前沒(méi)有事務(wù),則行為類(lèi)似于
REQUIRED
。 - 適用于需要嵌套事務(wù)支持的場(chǎng)景。
@Transactional(propagation = Propagation.NESTED)
public void transactionalMethod() {// 事務(wù)處理邏輯
}
Spring 事務(wù)使用
在 Spring 中,事務(wù)的實(shí)現(xiàn)有兩種:一種是聲明式事務(wù)管理,也就是通過(guò)@Transactional
注解聲明;另一種是編程式事務(wù)管理,也就是通過(guò)程序?qū)崿F(xiàn)。但不管是聲明式事務(wù)管理還是編程式事務(wù)管理,都需要做的事情就是:
- 配置 DataSource 數(shù)據(jù)源
- 配置 TransactionManagement 事務(wù)管理器
代碼就不在這里羅列了,可以參考一下我的github項(xiàng)目 中TransactionModuleApplicationConfig
類(lèi)。
聲明式事務(wù)管理
@Transactional 詳解
@Transactional
是 Spring 框架中用于聲明事務(wù)屬性的注解。我們可以通過(guò)在方法或類(lèi)上添加@Transactional
注解,來(lái)定義事務(wù)的行為,如隔離級(jí)別、傳播行為、超時(shí)的。
我們先來(lái)來(lái)看下該注解的接口定義:
package org.springframework.transaction.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";String[] label() default {};Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;String timeoutString() default "";boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};}
- value 與 transaction : 用于指定事務(wù)管理器的名稱(chēng),表示使用哪個(gè)事務(wù)管理器。這對(duì)于配置多個(gè)事務(wù)管理器的場(chǎng)景很有用。
- label : 用于定義事務(wù)的標(biāo)簽,這是在 Spring 5.3 版本引入的新特性。具體如何使用標(biāo)簽取決于實(shí)現(xiàn)的事務(wù)管理器實(shí)現(xiàn)。
- propagation : 用于指定事務(wù)方法的傳播行為,決定事務(wù)方法如何與已存在的事務(wù)進(jìn)行交互。
- 可選的事務(wù)傳播行為包括 REQUIRED,REQUIRED_NEW,SUPPORTS,NOT_SUPPORTS,NEVER,MANDATORY,NESTED。
- 默認(rèn)為 REQUIRED。
- isolation : 用于指定事務(wù)的隔離級(jí)別,控制多個(gè)事務(wù)之間的相互影響。
- 可選的事務(wù)隔離級(jí)別包括 READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLE。
- 默認(rèn)為 DEFAULT,取決于 JDBC 的事務(wù)隔離級(jí)別,依賴(lài)于數(shù)據(jù)庫(kù)。
- timeout : 指定事務(wù)的超時(shí)時(shí)間,單位為秒。如果事務(wù)執(zhí)行時(shí)間超過(guò)設(shè)定的超時(shí)時(shí)間,將會(huì)被回滾。默認(rèn)為
-1
,表示沒(méi)有超時(shí)時(shí)間。 - timeoutString : 允許將超時(shí)時(shí)間表示為字符串,例如使用占位符。作用同 timeout ,控制事務(wù)的執(zhí)行時(shí)間。
- readOnly : 用于指定事務(wù)是否為只讀事務(wù)。如果設(shè)置為
true
,表示只進(jìn)行讀取數(shù)據(jù)庫(kù)操作,可以?xún)?yōu)化事務(wù)(無(wú)需鎖定資源、減少回滾風(fēng)險(xiǎn)、提高并發(fā)性能、降低資源消耗、數(shù)據(jù)庫(kù)優(yōu)化器的選擇)。 - rollbackFor 與 rollbackForClassName : 用于指定在哪些異常情況下會(huì)回滾事務(wù),可以執(zhí)行異常類(lèi)型的類(lèi)型數(shù)組。
- noRollbackFor 與 noRollbackForClassName : 用于指定哪些異常下不會(huì)回滾事務(wù),可以指定異常的類(lèi)型數(shù)組。
使用示例
我們來(lái)看下聲明式事務(wù)管理的示例:
package com.markus.spring.transaction.service;import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;import java.util.List;/*** @author: markus* @date: 2024/2/3 10:19 PM* @Description:* @Blog: https://markuszhang.com* It's my honor to share what I've learned with you!*/
@Service
public class SpringTransactionService {@Autowiredprivate UserDao userDao;// 事務(wù)傳播行為 默認(rèn) REQUIRED@Transactional(rollbackFor = IllegalArgumentException.class, noRollbackFor = IllegalStateException.class)public void method() {User user = new User();long currentTime = System.currentTimeMillis();System.out.println("currentTime : " + currentTime);user.setName("帥氣的小張 " + currentTime);user.setAge(25);user.setAddress("山東菏澤");// 向數(shù)據(jù)庫(kù)中插入一條數(shù)據(jù)userDao.insertUser(user);// 拋出該異常會(huì)回滾
// throw new IllegalArgumentException("違規(guī)參數(shù)");// 拋出該異常不會(huì)回滾throw new IllegalStateException("違規(guī)狀態(tài)");}@Transactional(readOnly = true)public List<User> queryUsers() {return userDao.queryUsers(0);}}
package com.markus.spring.transaction.service;import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.transaction.config.TransactionModuleApplicationConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;/*** @author: markus* @date: 2024/2/3 10:22 PM* @Description:* @Blog: https://markuszhang.com* It's my honor to share what I've learned with you!*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TransactionModuleApplicationConfig.class)
public class TransactionServiceTest {@Autowiredprivate SpringTransactionService springTransactionService;@Testpublic void testMethod() {springTransactionService.method();}@Testpublic void testQueryUsers() {List<User> users = springTransactionService.queryUsers();users.forEach(System.out::println);}
}
編程式事務(wù)管理
編程式事務(wù)管理
是通過(guò)編寫(xiě)代碼顯示管理事務(wù)的一種方式,相對(duì)于聲明式事務(wù)管理
,它更加靈活,但也需要我們更深入地理解事務(wù)管理的細(xì)節(jié)。對(duì)于一些底層 API 我們不在此處贅述,重點(diǎn)講述如何通過(guò)代碼來(lái)顯示管理事務(wù)。
如何使用
我們直接上代碼:
package com.markus.spring.transaction.service;import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;import static com.markus.spring.data.jdbc.domain.entity.User.createUser;/*** @author: markus* @date: 2024/2/4 12:17 AM* @Description:* @Blog: https://markuszhang.com* It's my honor to share what I've learned with you!*/
@Service
public class ProgrammaticTransactionService {@Autowiredprivate UserDao userDao;@Autowiredprivate TransactionTemplate transactionTemplate;public void method() {User user = createUser();transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus status) {try {userDao.insertUser(user);} catch (Exception e) {// 捕獲異常,并將事務(wù)回滾status.setRollbackOnly();// 并將異常跑出去throw e;}}});}
}
在上面聲明式事務(wù)管理
中,我們可以指定一些屬性;同樣地,編程式事務(wù)管理
也可以通過(guò)TransactionTemplate
設(shè)置。
Spring 事務(wù)實(shí)現(xiàn)
大家在閱讀完上面的內(nèi)容后,應(yīng)該對(duì)事務(wù)
有了一定的了解和如何使用。想必大家對(duì)其底層的實(shí)現(xiàn)機(jī)制有一定的興趣,接下來(lái),我們將深入探討 Spring 事務(wù)管理的核心組件和關(guān)鍵類(lèi),解析其工作原理,通過(guò)深入源碼的閱讀和分析,我們將更好地的理解 Spring 事務(wù)管理的內(nèi)部機(jī)制,為更高效、更安全地應(yīng)用事務(wù)管理功能提供基礎(chǔ)。
在學(xué)習(xí)源碼前有必要提及的是:你需要掌握 Spring AOP 以及 Bean 生命周期的相關(guān)知識(shí)。因?yàn)?Spring 事務(wù)是在 Bean 生命周期環(huán)節(jié)對(duì)符合條件的 Bean 進(jìn)行代理,通過(guò) AOP 對(duì)類(lèi)或者方法進(jìn)行增強(qiáng)。
核心組件和關(guān)鍵類(lèi)
TransactionManagement
它是在 Spring 5.2 版本被提出來(lái)的一個(gè)用來(lái)統(tǒng)一表示傳統(tǒng)事務(wù)管理和響應(yīng)式事務(wù)管理的標(biāo)記接口。其實(shí)現(xiàn)類(lèi)如下所示:
我們重點(diǎn)來(lái)看 PlatformTransactionManagement 及其相關(guān)派生類(lèi)。PlatformTransactionManagement 定義了 事務(wù)管理 的基本操作。
public interface PlatformTransactionManager extends TransactionManager {/*** 獲取事務(wù)*/TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;/*** 事務(wù)提交*/void commit(TransactionStatus status) throws TransactionException;/*** 事務(wù)回滾*/void rollback(TransactionStatus status) throws TransactionException;}
對(duì)于幾個(gè)比較關(guān)鍵類(lèi)派生類(lèi)介紹:
- DataSourceTransactionManagement:它是基于 JDBC 的事務(wù)管理器,適用于單一的 JDBC 數(shù)據(jù)源
- JdbcTransactionManagement:它繼承與DataSourceTransactionManagement,與之不同的是,它更加靈活,可以與多個(gè)不同的 JDBC 數(shù)據(jù)源進(jìn)行交互。
- JtaTransactionManagement:它是基于 JTA(Java Transaction API)的事務(wù)管理器,支持分布式事務(wù)。比如多個(gè)不同的事務(wù)資源,如數(shù)據(jù)庫(kù)、消息隊(duì)列等。
InfrastructureAdvisorAutoProxyCreator
InfrastructureAdvisorAutoProxyCreator 用于自動(dòng)創(chuàng)建 AOP 的代理來(lái)應(yīng)用通用的基礎(chǔ)設(shè)施增強(qiáng)器(Advisors),通常用于聲明式事務(wù)管理、安全性檢查、性能監(jiān)控方面。本篇文章介紹的正是他對(duì)于聲明式事務(wù)管理方面的應(yīng)用。
通過(guò)上圖可以看到,InfrastructureAdvisorAutoProxyCreator 是 AOP 核心組件 AbstraAutoProxyCreator 的實(shí)現(xiàn)之一,目的就是創(chuàng)建 AOP 代理。
TransactionInterceptor
作用如其名,事務(wù)攔截器。是的,它實(shí)現(xiàn)了 AOP 的概念,用于在方法調(diào)用前后織入事務(wù)管理的邏輯。
AnnotationTransactionAttributeSource
AnnotationTransactionAttributeSource 是 Spring 框架用于解析 @Transaction 注解的類(lèi)之一,它實(shí)現(xiàn)了 TransactinAttributeSource 接口,用于從注解中解析事務(wù)屬性。
BeanFactoryTransactionAttributeSourceAdvisor
BeanFactoryTransactionAttributeSourceAdvisor 是 Spring 框架中用于基于 BeanFactory 的事務(wù)屬性源的增強(qiáng)器(Advisor)。它的作用是根據(jù)配置的事務(wù)屬性源(TransactionAttributeSource)和事務(wù)攔截器(MethodInterceptor),為目標(biāo)方法生成事務(wù)增強(qiáng)器,實(shí)現(xiàn)方法的事務(wù)管理。
工作原理
簡(jiǎn)單用一句話描述原理就是:Spring 框架通過(guò) AOP 實(shí)現(xiàn)了對(duì)業(yè)務(wù)方法執(zhí)行的事務(wù)管理。詳細(xì)來(lái)講就比較復(fù)雜了,需要大家掌握 Spring Bean 生命周期、AOP 攔截、事務(wù)生命周期等。
由于比較復(fù)雜,所以對(duì)于 Bean 何時(shí)以及如何被代理,我就簡(jiǎn)單說(shuō)下即可,來(lái)重點(diǎn)講述下 Spring 框架如何進(jìn)行的事務(wù)管理。
對(duì)于本小節(jié)的敘述,如以下所示:
- 簡(jiǎn)單說(shuō)下目標(biāo) Bean 何時(shí)以及如何被代理
- 詳細(xì)說(shuō)下事務(wù)構(gòu)建和事務(wù)執(zhí)行
- TransactionInterceptor#invoke() 流程介紹
自動(dòng)代理
Spring 通過(guò) InfrastructureAdvisorAutoProxyCreator 實(shí)現(xiàn)對(duì)目標(biāo) Bean 對(duì)象的代理。具體入口在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization,下面來(lái)簡(jiǎn)單介紹下該方法的執(zhí)行邏輯。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {// 獲取 cacheKey,設(shè)計(jì)了緩存來(lái)加速 Advisor 的構(gòu)建。// 可以參考 // private final Map<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);// private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 構(gòu)造 Advisor 集合并創(chuàng)建出代理return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果 beanName 不為空并且設(shè)置了自定義的目標(biāo)對(duì)象,則不需要被代理。if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 緩存優(yōu)化,歷史處理過(guò)該 Bean 并且知道不需要被代理,直接返回。if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 如果是 Spring Framework 基礎(chǔ)設(shè)施 Bean 或者 判斷該 Bean 需要跳過(guò),則不需要被代理if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 獲取 Advice 數(shù)據(jù)集合并創(chuàng)建最終的代理// getAdvicesAndAdvisorsForBean 方法是一個(gè)查找 IoC 容器中 Advice Bean 的方法。// 而我們前面說(shuō)的 BeanFactoryTransactionAttributeSourceAdvisor 就是在這里獲取到的。Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 創(chuàng)建代理。Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());// 返回代理。return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}
上面我們看到了 Spring 是如何給 Bean 進(jìn)行代理的,可能大家會(huì)有疑問(wèn):怎么判斷當(dāng)前 Bean 是否可以被代理呢?
這就是 AOP 框架中 Pointcut 組件來(lái)決策的,針對(duì)于事務(wù)功能上,對(duì)應(yīng)了 BeanFactoryTransactionAttributeSourceAdvisor,我們前面已經(jīng)介紹過(guò),它是為目標(biāo)方法生成事務(wù)增強(qiáng)器,其中它就包含 Pointcut 實(shí)現(xiàn)即 TransactionAttributeSourcePointcut。我們來(lái)看下它的內(nèi)部組成,來(lái)探究下:它是如何判斷當(dāng)前 Bean 是否應(yīng)該被代理。
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {protected TransactionAttributeSourcePointcut() {setClassFilter(new TransactionAttributeSourceClassFilter());}// 判斷 是否被代理 的核心方法@Overridepublic boolean matches(Method method, Class<?> targetClass) {// tas.getTransactionAttribute 獲取 @Transactional 注解元信息,如果該方法攜帶相關(guān)數(shù)據(jù),說(shuō)明需要被事務(wù)管理。TransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);}/*** Obtain the underlying TransactionAttributeSource (may be {@code null}).* To be implemented by subclasses.*/@Nullableprotected abstract TransactionAttributeSource getTransactionAttributeSource();/*** 用于過(guò)濾不需要事務(wù)管理的類(lèi)*/private class TransactionAttributeSourceClassFilter implements ClassFilter {@Overridepublic boolean matches(Class<?> clazz) {if (TransactionalProxy.class.isAssignableFrom(clazz) ||TransactionManager.class.isAssignableFrom(clazz) ||PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {return false;}// 判斷 當(dāng)前類(lèi)是否是攜帶 @Transactional 注解的候選類(lèi)(在類(lèi)型、方法上)TransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.isCandidateClass(clazz));}}}
通過(guò) TransactionAttributeSourcePointcut 的處理,我們可以得出上面的結(jié)論,即 Spring 框架通過(guò) TransactinAttributeSourcePointcut 來(lái)篩選目標(biāo)方法,來(lái)對(duì)方法進(jìn)行事務(wù)管理邏輯增強(qiáng)。
通過(guò)上面的討論,在自動(dòng)代理環(huán)節(jié),我們能得出這樣一個(gè)結(jié)論:Spring 框架通過(guò) AOP 實(shí)現(xiàn)了目標(biāo) Bean 的方法增強(qiáng),增加事務(wù)管理的邏輯。我們把其核心類(lèi)串一下:
- Spring 向 IoC 容器注冊(cè) InfrastructureAdvisorAutoProxyCreator Bean,
- 該基礎(chǔ)設(shè)施 Bean 可以在
業(yè)務(wù)Bean
生命周期的初始化后(postProcessAfterInitialization)
環(huán)節(jié),對(duì)目標(biāo)業(yè)務(wù) Bean 進(jìn)行攔截,獲取容器注冊(cè)過(guò)的事務(wù)增強(qiáng)器(BeanFactoryTransactionAttributeSourceAdvisor)并為目標(biāo) Bean 生成代理,完成事務(wù)管理邏輯增強(qiáng)。 - 而 BeanFactoryTransactionAttributeSourceAdvisor 中的 TransactionAttributeSourcePointcut 完成目標(biāo) Bean 及其方法的篩選攔截。
執(zhí)行事務(wù)
目前,我們已經(jīng)清楚業(yè)務(wù) Bean 如何被事務(wù)管理增強(qiáng),接下來(lái)我們繼續(xù)討論當(dāng)應(yīng)用程序調(diào)用被事務(wù)管理的方法時(shí),事務(wù)是如何被執(zhí)行的。
在核心組件介紹 BeanFactoryTransactionAttributeSourceAdvisor 時(shí),它是一個(gè) PoicutcutAdvisor,也就是說(shuō)它是由 Pointcut 和 Advice 組成。在前面說(shuō)目標(biāo)方法篩選時(shí)Pointcut
起到了作用。那么 Advice 則是在執(zhí)行事務(wù)時(shí)起到作用,其實(shí)現(xiàn)則是 TransactionInterceptor。為什么說(shuō)是它的,我們可以在這段代碼中找到答案。
private static class AopAutoProxyConfigurer {public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {Object eleSource = parserContext.extractSource(element);// Create the TransactionAttributeSource definition.RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");sourceDef.setSource(eleSource);sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);// Create the TransactionInterceptor definition.RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);interceptorDef.setSource(eleSource);interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registerTransactionManager(element, interceptorDef);interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);// Create the TransactionAttributeSourceAdvisor definition.RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);advisorDef.setSource(eleSource);advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);if (element.hasAttribute("order")) {advisorDef.getPropertyValues().add("order", element.getAttribute("order"));}parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));parserContext.registerComponent(compositeDef);}}}
至此,我們已經(jīng)清晰代理方法的入口了,即 org.springframework.transaction.interceptor.TransactionInterceptor#invoke。接下來(lái),我們就重點(diǎn)分析該方法。
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSource tas = getTransactionAttributeSource();final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);final TransactionManager tm = determineTransactionManager(txAttr);if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {// 針對(duì) 響應(yīng)式編程 的處理,我們不關(guān)注,先刪除,大家有興趣可以去看下}// 獲取事務(wù)管理器PlatformTransactionManager ptm = asPlatformTransactionManager(tm);// 獲取目標(biāo)業(yè)務(wù)方法final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// 這里就是聲明式事務(wù)管理的處理邏輯 @Transactional 注解// Standard transaction demarcation with getTransaction and commit/rollback calls.// 獲取 事務(wù),這里面包括了事務(wù)的傳播行為、事務(wù)隔離級(jí)別以及事務(wù)的提交、回滾等信息TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.// 通常的 攔截器不只有一個(gè),是一個(gè)鏈?zhǔn)降摹H缟厦孀髡咚?#xff0c;這是一個(gè) Around Advice,將會(huì)調(diào)用攔截器鏈中下一個(gè)攔截器。// 但 返回的結(jié)果 retVal 是 目標(biāo)方法的執(zhí)行結(jié)果。retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exception// 這里的處理則是 對(duì)目標(biāo)異常進(jìn)行回滾。目標(biāo)異常外的異常不進(jìn)行回滾completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {// 清除事務(wù)信息在當(dāng)前線程下cleanupTransactionInfo(txInfo);}if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}// 目標(biāo)方法執(zhí)行成功后返回結(jié)果 進(jìn)行事務(wù)提交commitTransactionAfterReturning(txInfo);// 將 目標(biāo)值 返回。return retVal;}else {// 針對(duì) CallbackPreferringPlatformTransactionManager 事務(wù)管理的處理,多處理 編程式事務(wù)管理,我們暫時(shí)不關(guān)注,先刪除。}
}
本文總結(jié)
我們總結(jié)一下,本篇文章從事務(wù)基礎(chǔ)
介紹開(kāi)始,講述了什么是事務(wù)、隔離級(jí)別、傳播行為,接著又講述了 Spring 事務(wù)的使用即聲明式事務(wù)管理
以及編程式事務(wù)管理
,最后又講述的 Spring 的 事務(wù)實(shí)現(xiàn)原理包括核心組件、關(guān)鍵類(lèi)以及事務(wù)的工作原理。
至此,關(guān)于事務(wù)的知識(shí)點(diǎn)就講述完了。如果還有其他沒(méi)有覆蓋到的地方,歡迎交流。😄