帶你讀懂Spring的事務(wù)傳播行為
吊打 ThreadLocal,談?wù)凢astThreadLocal為啥能這么快? 一個Github項目搞定微信、QQ、支付寶等第三方登錄 注解+反射優(yōu)雅的實現(xiàn)Excel導(dǎo)入導(dǎo)出(通用版) Fluent Mybatis 牛逼! Nginx 常用配置清單 這玩意比ThreadLocal叼多了,嚇得我趕緊分享出來。
來源:blog.csdn.net/xuan_lu/article/details/106006755
一、概念
首先簡單了解一下Spring中事務(wù)傳播行為是什么?聽起來很高端,但是真正用起來的時候,稍有不慎,就會讓自己陷入困境之中,所以在使用之前,我們必須要十分耐心認真的學(xué)習(xí)它。
從名字理解起來,事務(wù)傳播行為,既然為傳播就肯定發(fā)生在兩個實體之間,否則單個實體又如何發(fā)生行為呢。通俗點講就是“一個巴掌拍不響”。下面進入正規(guī)話題。
事務(wù)傳播行為主要用來描述由某一個事務(wù)傳播行為修飾的方法被嵌套進另一個方法的事務(wù)中,該事務(wù)如何傳播。這個概述可能不好理解,換句話就是當一個事務(wù)方法被另一個事務(wù)方法調(diào)用時,這個事務(wù)方法應(yīng)該如何進行。
下面用代碼+文字說明解釋上面的概念。
@Transaction(Propagation=XXX)
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
methodA事務(wù)方法調(diào)用methodB事務(wù)方法時,methodB是繼續(xù)在調(diào)用者methodA的事務(wù)中運行呢,還是為自己開啟一個新事務(wù)運行,這就是由methodB的事務(wù)傳播行為決定的。
注意:methodA和methodB都加了事務(wù)。methodA()也可以不用開啟事務(wù),某一個事務(wù)傳播行為修飾的方法并不是必須要在開啟事務(wù)的外圍方法中調(diào)用。
二、Spring中七種事務(wù)傳播行為
通過上面?zhèn)未a加文字解釋了解到事務(wù)傳播行為的相關(guān)概念,下面就要學(xué)習(xí)事務(wù)傳播行為的類型和運行機制。

驗證
Propagation_Required
調(diào)用者方法不存在事務(wù)傳播行為
1.調(diào)用者方法內(nèi)部存在異常時,被調(diào)用者方法均存在事務(wù),那么結(jié)果如何呢?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertClass(ClassDo classDo) {
classMapper.insertClass(classDo);
System.out.println("----------------------->Class插入成功!");
}
單元測試
@SpringBootTest
@RunWith(SpringRunner.class)
public class PropagationTest {
private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
static {
studentDo.setClassId(1);
studentDo.setStudentName("student1");
studentDo.setAddress("測試");
classDo.setClassName("class_1");
classDo.setClassNo("Class01");
}
@Autowired
private StudentService studentService;
@Autowired
private ClassService classService;
@Test
public void insertTest() {
studentService.insertStudent(studentDo);
classService.insertClass(classDo);
}
}

結(jié)果:兩條數(shù)據(jù)均被插入數(shù)據(jù)庫。由于外部方法并沒有開啟事務(wù),所以內(nèi)部方法均在自己的事務(wù)提交或者回滾,因此外部方法中存在異常,內(nèi)部方法事務(wù)不會回滾。
2.被調(diào)用者均存在事務(wù),而在被調(diào)用者中存在異常,那么結(jié)果如何?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
//此方法中拋出異常
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertClassByException(ClassDo classDo) throws CustomException {
classMapper.insertClass(classDo);
throw new CustomException();
}
單元測試代碼
private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
static {
studentDo.setClassId(2);
studentDo.setStudentName("student2");
studentDo.setAddress("測試2");
classDo.setClassName("class_2");
classDo.setClassNo("Class02");
}
@Test
public void insertExceptionTest() throws CustomException {
studentService.insertStudent(studentDo);
classService.insertClassByException(classDo);
}

結(jié)果:第一數(shù)據(jù)成功插入,第二條數(shù)據(jù)因異常存在,事務(wù)回滾。內(nèi)部方法均在各個的事務(wù)中運行,class事務(wù)回滾,student數(shù)據(jù)不會受到影響。

結(jié)合1和2我們可以得出結(jié)論
1:通過這兩個方法我們證明了在外圍方法未開啟事務(wù)的情況下Propagation_Required修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。
2.調(diào)用者開啟事務(wù)傳播行為
內(nèi)部方法同上
//單元測試方法
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertInnerExceptionThrowsTest() throws CustomException {
studentService.insertStudent(studentDo);
classService.insertClassByException(classDo);
}
結(jié)果:內(nèi)部方法雖然存在事務(wù)傳播行為,但是外部方法也存在事務(wù)且使用Propagation.REQUIRED修飾,所有內(nèi)部方法不會新建事務(wù),直接運行在當前事務(wù)中,所以student、class均會被回滾。
學(xué)習(xí)資料:Java進階視頻資源
3.調(diào)用者開啟事務(wù)傳播行為,但是捕獲內(nèi)部方法異常
/**
* 內(nèi)部方法發(fā)生異常情況,外部方法即使捕獲處理該異常,依然數(shù)據(jù)會被回滾
*/
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertInnerExceptionTest() {
studentService.insertStudent(studentDo);
try {
classService.insertClassByException(classDo);
} catch (CustomException e) {
e.printStackTrace();
}
}
結(jié)果:外圍方法開啟事務(wù),內(nèi)部方法加入外圍方法事務(wù),內(nèi)部方法拋出異?;貪L,即使方法被catch不被外圍方法感知,整個事務(wù)依然回滾。同2一樣,調(diào)用者方法執(zhí)行操作和被調(diào)用者中的方法操作結(jié)果均被回滾。
Propagation_Supports
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertSupportsTest() {
studentService.insertStudent(studentDo);
}
解釋:如果單純的調(diào)用insertStudent()方法,則以非事務(wù)執(zhí)行,即使后面存在異常情況,執(zhí)行操作結(jié)果不會觸發(fā)事務(wù)回滾機制。當調(diào)用insertSupportsTest()方法時,該方法以REQUIRED修飾,則會新建一個事務(wù),內(nèi)部調(diào)用insertStudent()方法,所以insertStudent()會加入到當前事務(wù)中執(zhí)行。
Propagation_Mandatory
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertSupportsTest() {
studentService.insertStudent(studentDo);
}

解釋結(jié)果:MANDATORY表示被修飾的方法必須在事務(wù)中運行。當單獨調(diào)用insertStudent時,因為當前沒有一個活動的事務(wù),則會拋出異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當調(diào)用insertSupportsTest時,insertStudent則加入到insertSupportsTest的事務(wù)中,事務(wù)地執(zhí)行。
Propagation_Required_New
表示被修飾的方法必須運行在它自己的事務(wù)中。一個新的事務(wù)會被啟動。如果調(diào)用者存在當前事務(wù),則在該方法執(zhí)行期間,當前事務(wù)會被掛起。
private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
static {
studentDo.setClassId(2);
studentDo.setStudentName("requireNew");
studentDo.setAddress("requireNew");
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void insertClassByException(ClassDo classDo) throws CustomException {
classMapper.insertClass(classDo);
throw new CustomException();
}
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertInnerExceptionTest() {
studentService.insertStudent(studentDo);
try {
classService.insertClassByException(classDo);
} catch (CustomException e) {
e.printStackTrace();
}
}
結(jié)果解析:insertStudent(),insertClassByException()方法執(zhí)行時,外部方法事務(wù)被掛起,內(nèi)部方法會新建事務(wù),直至該方法執(zhí)行結(jié)束,恢復(fù)外部方法事務(wù)執(zhí)行。兩者之間事務(wù)存在隔離性,insertClassByException()方法遇到異常,觸發(fā)事務(wù)回滾機制,但insertStudent()執(zhí)行結(jié)果并受到影響。學(xué)習(xí)資料:Java進階視頻資源
如圖所示:


Propagation_Not_Supported
表示被修飾的方法不應(yīng)該運行在事務(wù)中。如果調(diào)用者存在當前事務(wù),則該方法運行期間,當前事務(wù)將被掛起。
private final static ClassDo classDo = new ClassDo();
static {
classDo.setClassName("notSupport");
classDo.setClassNo("notSupport");
}
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void insertClassByException(ClassDo classDo) throws CustomException {
classMapper.insertClass(classDo);
throw new CustomException();
}
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertInnerExceptionTest() {
try {
classService.insertClassByException(classDo);
} catch (CustomException e) {
e.printStackTrace();
}
}
結(jié)果解釋:即使外部方法開啟事務(wù),但是insertClassByException()執(zhí)行,當前事務(wù)會掛起,not_support以非事務(wù)方式運行,所以即使遇到異常情況,執(zhí)行結(jié)果也不會觸發(fā)回滾。

Propagation_Never
表示被修飾的方法不應(yīng)該運行事務(wù)上下文中。如果調(diào)用者或者該方法中存在一個事務(wù)正在運行,則會拋出異常。
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertInnerExceptionTest() {
studentService.insertStudent(studentDo);
}
結(jié)果如圖:

Propagation_Nested
表示當前方法已經(jīng)存在一個事務(wù),那么該方法將會在嵌套事務(wù)中運行。 嵌套的事務(wù)可以獨立與當前事務(wù)進行單獨地提交或者回滾。 如果當前事務(wù)不存在,那么其行為與Propagation_Required一樣。 嵌套事務(wù)的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時,會回滾內(nèi)層事務(wù)所做的動作。而內(nèi)層事務(wù)操作失敗并不會引起外層事務(wù)的回滾。
1.外部未開啟事務(wù)時,內(nèi)部方法則新建事務(wù)執(zhí)行
private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
static {
studentDo.setClassId(2);
studentDo.setStudentName("NESTED");
studentDo.setAddress("NESTED");
classDo.setClassName("NESTED");
classDo.setClassNo("NESTED");
}
@Test
public void insertTest() {
studentService.insertStudent(studentDo);
classService.insertClass(classDo);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void insertStudent(StudentDo studentDo) {
studentMapper.insertStudent(studentDo);
System.out.println("----------------------->Student插入成功!");
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void insertClass(ClassDo classDo) {
classMapper.insertClass(classDo);
System.out.println("----------------------->Class插入成功!");
}
結(jié)果:


2.外部方法開啟事務(wù):
如果外部方法發(fā)生異常,則內(nèi)部事務(wù)一起發(fā)生回滾操作; 如果外部無異常情況,內(nèi)部被調(diào)用方法存在異常情況,則內(nèi)部方法獨立回滾(疑問點???我用以下實例驗證,但是外部方法也一樣被回滾了,請各位大佬給與解答);
//單測代碼
private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
static {
studentDo.setClassId(2);
studentDo.setStudentName("NESTED_InnerException");
studentDo.setAddress("NESTED_InnerException");
classDo.setClassName("NESTED_InnerException");
classDo.setClassNo("NESTED_InnerException");
}
@Test
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void insertInnerExceptionThrowsTest() throws CustomException {
studentMapper.insertStudent(studentDo);
classService.insertClassByException(classDo);
}
//NESTED事務(wù)傳播行為
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void insertClassByException(ClassDo classDo) throws CustomException {
classMapper.insertClass(classDo);
throw new RuntimeException();
}
源代碼傳送門:
https://github.com/stream-source/stream-source/tree/master/informal-essay/src/test/java/com/qxy
推薦一些很不錯的計算機學(xué)習(xí)教程,包括:數(shù)據(jù)結(jié)構(gòu)、算法、計算機網(wǎng)絡(luò)、操作系統(tǒng)、Java(spring、springmvc、springboot、springcloud等)等等 ,全部收集于網(wǎng)絡(luò),如果有侵權(quán),請聯(lián)系刪除! 下面是部分截圖: 獲取方式 點擊下方公眾號,回復(fù):好好學(xué)Java,即可獲取。

