1、背景
在我們的日常開發(fā)中,經(jīng)常會(huì)存在在一個(gè)Service層中調(diào)用另外一個(gè)Service層的方法。比如:我們有一個(gè)TaskService,里面有一個(gè)execTask方法,且這個(gè)方法存在事務(wù),這個(gè)方法在執(zhí)行完之后,需要調(diào)用LogService的insertLog方法記錄一條日志,這個(gè)方法上也有事務(wù),不管日志記錄成功還是失敗,都不能影響execTask方法的執(zhí)行。因此我們很容易寫出如下代碼。
@Transactional
public void execTaskV1(){
log.info("開始執(zhí)行任務(wù)");
try {
logService.insertLogV1();
} catch (Exception e) {
log.error("添加日志出現(xiàn)錯(cuò)誤");
}
log.info("完成任務(wù)執(zhí)行");
}
思考: 上方的代碼,如果insertLogV1跑出了異常,execTaskV1方法的事務(wù)可以正常提交嗎?
2、異常是如何實(shí)現(xiàn)出現(xiàn)的
1、了解Spring事務(wù)的傳播屬性
| 傳播行為 | 描述 | 應(yīng)用場景 | 行為特點(diǎn) |
|---|---|---|---|
Propagation.REQUIRED |
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則啟動(dòng)一個(gè)新的事務(wù)。 | 大多數(shù)場景,如多個(gè)方法需要在同一個(gè)事務(wù)中完成。 | - 如果當(dāng)前事務(wù)存在,方法執(zhí)行在當(dāng)前事務(wù)上下文中。 - 如果當(dāng)前事務(wù)不存在,創(chuàng)建新事務(wù)。 |
Propagation.SUPPORTS |
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)方式執(zhí)行。 | 對(duì)事務(wù)支持沒有強(qiáng)制要求的場景,如只讀查詢。 | - 如果當(dāng)前事務(wù)存在,方法執(zhí)行在當(dāng)前事務(wù)上下文中。 - 如果當(dāng)前事務(wù)不存在,以非事務(wù)方式執(zhí)行。 |
Propagation.MANDATORY |
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則拋出異常。 | 必須在一個(gè)已存在的事務(wù)中執(zhí)行的場景。 | - 必須在已有事務(wù)中執(zhí)行,否則拋出 IllegalTransactionStateException。 |
Propagation.REQUIRES_NEW |
每次調(diào)用該方法時(shí)都會(huì)啟動(dòng)一個(gè)新的事務(wù)。當(dāng)前事務(wù)(如果有)會(huì)被掛起。 | 需要獨(dú)立事務(wù)的場景,如日志記錄或獨(dú)立的業(yè)務(wù)操作。 | - 總是創(chuàng)建新事務(wù)。 - 當(dāng)前事務(wù)(如果有)會(huì)被掛起,直到新事務(wù)完成。 |
Propagation.NOT_SUPPORTED |
總是以非事務(wù)方式執(zhí)行,并且暫停當(dāng)前事務(wù)(如果有)。 | 不需要事務(wù)的場景,如簡單的查詢操作。 | - 總是以非事務(wù)方式執(zhí)行。 - 暫停當(dāng)前事務(wù)(如果有)。 |
Propagation.NEVER |
總是以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。 | 嚴(yán)格禁止事務(wù)的場景,如某些非事務(wù)性操作。 | - 必須在非事務(wù)上下文中執(zhí)行,否則拋出 TransactionException。 |
Propagation.NESTED |
如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果當(dāng)前沒有事務(wù),則啟動(dòng)一個(gè)新的事務(wù)。 | 需要嵌套事務(wù)的場景,如復(fù)雜的業(yè)務(wù)流程中需要獨(dú)立的回滾點(diǎn)。 | - 如果當(dāng)前事務(wù)存在,創(chuàng)建一個(gè)嵌套事務(wù)(依賴于數(shù)據(jù)庫支持)。 - 如果當(dāng)前事務(wù)不存在,創(chuàng)建新事務(wù)。 |
2、模擬異常出現(xiàn)
Transaction rolled back because it has been marked as rollback-only 這個(gè)異常在上述的案例中是如何實(shí)現(xiàn)的呢?

從上圖中可知,出現(xiàn)了Transaction rolled back because it has been marked as rollback-only這個(gè)異常,那么這個(gè)異常是如何出現(xiàn)的呢?
其實(shí)這個(gè)是和Spring事務(wù)的傳播屬性Propagation有關(guān)。
默認(rèn)情況下@Transaction的傳播屬性是Propagation.REQUIRED, 即如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則啟動(dòng)一個(gè)新的事務(wù)。 在我們的例子中,事務(wù)的隔離級(jí)別都是Propagation.REQUIRED,即是在同一個(gè)事務(wù)中,因此insertLogV1方法拋出異常后,雖然上層捕獲到了,但其實(shí)這個(gè)時(shí)候這個(gè)事務(wù)已經(jīng)被標(biāo)記成回滾狀態(tài)了,因此事務(wù)無法提交成功。
如何解決: 只需要修改insertLogV1事務(wù)的傳播屬性為Propagation.REQUIRES_NEW即可。
3、完整代碼
完整代碼-https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/springboot-transaction-v1