手把手利用實(shí)戰(zhàn)代碼帶你讀懂Spring的事務(wù)傳播行為
概念
首先簡(jiǎn)單了解一下 Spring 中事務(wù)傳播行為是什么?聽起來(lái)很高端,但是真正用起來(lái)的時(shí)候,稍有不慎,就會(huì)讓自己陷入困境之中,所以在使用之前,我們必須要十分耐心認(rèn)真的學(xué)習(xí)它。
從名字理解起來(lái),事務(wù)傳播行為,既然為傳播就肯定發(fā)生在兩個(gè)實(shí)體之間,否則單個(gè)實(shí)體又如何發(fā)生行為呢。通俗點(diǎn)講就是“一個(gè)巴掌拍不響”。下面進(jìn)入正規(guī)話題。
?事務(wù)傳播行為主要用來(lái)描述由某一個(gè)事務(wù)傳播行為修飾的方法被嵌套進(jìn)另一個(gè)方法的事務(wù)中,該事務(wù)如何傳播。這個(gè)概述可能不好理解,換句話就是當(dāng)一個(gè)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),這個(gè)事務(wù)方法應(yīng)該如何進(jìn)行。
?
下面用代碼+文字說(shuō)明解釋上面的概念。
@Transaction(Propagation=XXX)
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
methodA 事務(wù)方法調(diào)用 methodB 事務(wù)方法時(shí),methodB 是繼續(xù)在調(diào)用者 methodA 的事務(wù)中運(yùn)行呢,還是為自己開啟一個(gè)新事務(wù)運(yùn)行,這就是由 methodB 的事務(wù)傳播行為決定的。
注意:methodA 和 methodB 都加了事務(wù)。methodA()也可以不用開啟事務(wù),某一個(gè)事務(wù)傳播行為修飾的方法并不是必須要在開啟事務(wù)的外圍方法中調(diào)用。
Spring 中七種事務(wù)傳播行為
通過(guò)上面?zhèn)未a加文字解釋了解到事務(wù)傳播行為的相關(guān)概念,下面就要學(xué)習(xí)事務(wù)傳播行為的類型和運(yùn)行機(jī)制。

驗(yàn)證
Propagation_Required
調(diào)用者方法不存在事務(wù)傳播行為
「1.調(diào)用者方法內(nèi)部存在異常時(shí),被調(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插入成功!");
}
單元測(cè)試
// 單元測(cè)試
@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("測(cè)試");
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ù)庫(kù)。由于外部方法并沒有開啟事務(wù),所以內(nèi)部方法均在自己的事務(wù)提交或者回滾,因此外部方法中存在異常,內(nèi)部方法事務(wù)不會(huì)回滾。
「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();
}
單元測(cè)試代碼
//單元測(cè)試代碼
private final static StudentDo studentDo = new StudentDo();
private final static ClassDo classDo = new ClassDo();
static {
studentDo.setClassId(2);
studentDo.setStudentName("student2");
studentDo.setAddress("測(cè)試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)部方法均在各個(gè)的事務(wù)中運(yùn)行,class 事務(wù)回滾,student 數(shù)據(jù)不會(huì)受到影響。

結(jié)合 1 和 2 我們可以得出結(jié)論:
通過(guò)這兩個(gè)方法我們證明了在外圍方法未開啟事務(wù)的情況下
Propagation_Required修飾的內(nèi)部方法會(huì)新開啟自己的事務(wù),且開啟的事務(wù)相互獨(dú)立,互不干擾。調(diào)用者開啟事務(wù)傳播行為。
內(nèi)部方法同上
//單元測(cè)試方法
@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)部方法不會(huì)新建事務(wù),直接運(yùn)行在當(dāng)前事務(wù)中,所以 student、class 均會(huì)被回滾。
調(diào)用者開啟事務(wù)傳播行為,但是捕獲內(nèi)部方法異常。
/**
* 內(nèi)部方法發(fā)生異常情況,外部方法即使捕獲處理該異常,依然數(shù)據(jù)會(huì)被回滾
*/
@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)部方法拋出異常回滾,即使方法被 catch 不被外圍方法感知,整個(gè)事務(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é)果不會(huì)觸發(fā)事務(wù)回滾機(jī)制。當(dāng)調(diào)用insertSupportsTest()方法時(shí),該方法以 REQUIRED 修飾,則會(huì)新建一個(gè)事務(wù),內(nèi)部調(diào)用insertStudent()方法,所以insertStudent()會(huì)加入到當(dāng)前事務(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ù)中運(yùn)行。當(dāng)單獨(dú)調(diào)用 insertStudent 時(shí),因?yàn)楫?dāng)前沒有一個(gè)活動(dòng)的事務(wù),則會(huì)拋出異常。
throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
當(dāng)調(diào)用 insertSupportsTest 時(shí),insertStudent 則加入到 insertSupportsTest 的事務(wù)中,事務(wù)地執(zhí)行。
Propagation_Required_New
表示被修飾的方法必須運(yùn)行在它自己的事務(wù)中。一個(gè)新的事務(wù)會(huì)被啟動(dòng)。如果調(diào)用者存在當(dāng)前事務(wù),則在該方法執(zhí)行期間,當(dāng)前事務(wù)會(huì)被掛起。
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í)行時(shí),外部方法事務(wù)被掛起,內(nèi)部方法會(huì)新建事務(wù),直至該方法執(zhí)行結(jié)束,恢復(fù)外部方法事務(wù)執(zhí)行。兩者之間事務(wù)存在隔離性,insertClassByException() 方法遇到異常,觸發(fā)事務(wù)回滾機(jī)制,但 insertStudent() 執(zhí)行結(jié)果并受到影響。
如圖所示:


Propagation_Not_Supported
表示被修飾的方法不應(yīng)該運(yùn)行在事務(wù)中。如果調(diào)用者存在當(dāng)前事務(wù),則該方法運(yùn)行期間,當(dāng)前事務(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í)行,當(dāng)前事務(wù)會(huì)掛起,not_support 以非事務(wù)方式運(yùn)行,所以即使遇到異常情況,執(zhí)行結(jié)果也不會(huì)觸發(fā)回滾。

Propagation_Never
表示被修飾的方法不應(yīng)該運(yùn)行事務(wù)上下文中。如果調(diào)用者或者該方法中存在一個(gè)事務(wù)正在運(yùn)行,則會(huì)拋出異常。
@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
表示當(dāng)前方法已經(jīng)存在一個(gè)事務(wù),那么該方法將會(huì)在嵌套事務(wù)中運(yùn)行。 嵌套的事務(wù)可以獨(dú)立與當(dāng)前事務(wù)進(jìn)行單獨(dú)地提交或者回滾。 如果當(dāng)前事務(wù)不存在,那么其行為與 Propagation_Required 一樣。 嵌套事務(wù)的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時(shí),會(huì)回滾內(nèi)層事務(wù)所做的動(dòng)作。而內(nèi)層事務(wù)操作失敗并不會(huì)引起外層事務(wù)的回滾。
「1.外部未開啟事務(wù)時(shí),內(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ā)生回滾操作; 如果外部無(wú)異常情況,內(nèi)部被調(diào)用方法存在異常情況,則內(nèi)部方法獨(dú)立回滾
//單測(cè)代碼
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();
}
最后,需要本文完整源碼的可以加我微信:codedq,免費(fèi)獲取!
