Spring事務管理中的異?;貪L是什么
記錄總結(jié)Spring核心知識點:事務使用與它的傳播機制
前言
這里不打算討論Spring底層源碼,只討論測試場景和總結(jié). 不斷整理讓大腦中的知識體系沉淀。
問題場景
某項目系統(tǒng)中,serviceA 中調(diào)用的 serviceB ,并且對 serviceB 進行 tryCache
@Service("testAService")
public class TestAServiceImpl implements TestAService {
@Resource
private TestAMapper testAMapper;
@Resource
private TestBService testBService;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void saveTestA(TestA entity) {
testAMapper.insertSelective(entity);
try {
testBService.saveTestB(new TestB());
} catch (Exception e) {
logger.error("調(diào)用B失敗", e);
}
// 模擬做其他的數(shù)據(jù)庫操作事情
testAMapper.updateSelective(entity);
}
}testBService 中模擬拋出異常:
@Service("testBService")
public class TestBServiceImpl mplements TestBService {
@Resource
private TestBMapper testBMapper;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
@Override
public void saveTestB(TestB entity) {
testBMapper.insertSelective(entity);
throw new RuntimeException("自定義異常");
}
}
問 在Controller層中調(diào)用 TestAService.saveTestA() 會怎么樣?
@Resource
private TestAService testAService;
@ApiOperation(value = "Spring事務嵌套測試")
@GetMapping("springTransactionTest")
public ResponseVO<NoBody> springTransactionTest() {
testAService.saveTestA(new TestA());
return ResponseVO.success();
}
答案是:
testAService testBService 中的數(shù)據(jù)庫操作 全部回滾,并且拋出的錯誤異常:
Transaction rolled back because it has been marked as rollback-only
原因是:
testBService.saveTestB 也增加了同樣的事務注解 @Transactional
且事務隔離機制為 “REQUIRED” ,因此 兩方法執(zhí)行期間為同一個數(shù)據(jù)庫事務,被同一個Spring事務管理器所管理著。
由于最開始開啟事務者為 testAService.saveTestA,則真正執(zhí)行回滾操作在 saveTestA 方法中 (Spring 規(guī)定了只有新創(chuàng)建的事務才會真正進行提交或回滾),
因此 saveTestB 方法中異常時只設置了當前事務狀態(tài)為 RollbackOnly
org.springframework.jdbc.datasource.DataSourceTransactionManager#doSetRollbackOnly
雖然 saveTestA 中 tryCache 了 saveTestB 中的異常,企圖吃掉異常信息讓 saveTestA 中的事務正常提交,但是 saveTestB 里面已經(jīng)設置了 當前事務狀態(tài)為 RollbackOnly, 出現(xiàn)了沖突矛盾!
因此事務全部回滾,并且拋出異常信息:
Transaction rolled back because it has been marked as rollback-only
Spring 管理事務的原理
首先,事務一般是關系型數(shù)據(jù)庫中的概念,主要目的就是 保證一系列的增刪改 SQL操作 要么全部成功,要么全部回滾。
MySQL中的事務管理
在MySQL中采用SQL命令進行事務管理:
- START TRANSACTION 或 BEGIN 或 SET autocommit = 0 開啟事務
- 執(zhí)行 CRUD
- COMMIT 提交事務
- ROLLBACK 回滾事務
這里重點說下 多條SQL在一個事務中,其中有部分SQL執(zhí)行失敗情況下,最終執(zhí)行結(jié)果是什么
> begin;
> insert_sql1 (insert into test1(id,name)value(1,'aaa')) ;
> insert_sql2 (insert into test2(id,name)value(1,'bbb')) ;
> commit/ rollback;
上面示意代碼,如果 insert_sql1 成功, insert_sql2 失敗時,請問 insert_sql1 最終是否插入成功?
答案是:
首先事務不會馬上回滾, 其次如果 此時執(zhí)行commit則 insert_sql1 會插入成功 ,如果執(zhí)行rollback則insert_sql1 會回滾。
那一般事務什么時候自動回滾或者自動提交?這里記錄一下常見場景:
- 如果事務執(zhí)行中出現(xiàn) DDL語句( alert create drop truncate等 ) 事務自動 commit;
- 如果事務執(zhí)行中又開啟了一個事務(又出現(xiàn) begin; sql命令)事務自動 commit;
- 如果執(zhí)行SQL的session 中途被關閉(SQL窗口關閉,服務器斷電等) 事務自動 rollback;
JDBC中的事務管理
JDBC中連接數(shù)據(jù)庫進行事務管理:
- 獲取連接 Connection con = DriverManager.getConnection()
- con.setAutoCommit(true/false); 開啟事務
- 執(zhí)行CRUD
- con.commit() ; 提交事務
- con.rollback(); 回滾事務
- 關閉連接 conn.close();
JDBC 事務管理的本質(zhì)還是連接了數(shù)據(jù)庫執(zhí)行各類數(shù)據(jù)庫中開啟關閉事務的SQL命令
Spring中的事務管理
Spring通過自身AOP切面功能,代理各個業(yè)務方法調(diào)用 JDBC中的方法進行開啟、關閉、提交、回滾事務等操作。
至于嵌套事務、各類傳播機制是如何實現(xiàn), 這里簡單總結(jié),雖然不能體現(xiàn)Spring 事務操作方面的強大,但可以很快有個大致理解。
Spring 通過 一個Map 存放了當前數(shù)據(jù)庫連接對象,這是為了解決根據(jù)設定的傳播機制 ( propagation ) 決定是否要新開一個事務,新開另外一個事務需要重新申請一個數(shù)據(jù)庫連接。
Spring 通過 數(shù)據(jù)庫事務中的 SAVEPOINT 保留點功能實現(xiàn) 嵌套事務的傳播機制。
這里記錄一下一個HTTP請求 從Controller層發(fā)起數(shù)據(jù)庫操作請求到回滾的log日志,用于加強理解:
@ApiOperation(value = "事務回滾測試")
@PostMapping("rollbackTest")
public ResponseVO<NoBody> rollbackTest() {
// 簡單模擬插入一條記錄
testAService.saveTestA(new TestA());
return ResponseVO.success();
}
[http-nio-9902-exec-5] o.s.web.servlet.DispatcherServlet : POST "/api/rollbackTest", parameters={}
[http-nio-9902-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.guzt.main.model.test.web.DbTestController#rollbackTest()
[http-nio-9902-exec-5] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.sdjictec.wms.main.model.test.service.impl.TestAServiceImpl.saveTestA]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.Exception
[http-nio-9902-exec-5] o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@7fec11ca] for JDBC transaction
[http-nio-9902-exec-5] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@7fec11ca] to manual commit
[http-nio-9902-exec-5] c.s.w.m.m.t.d.T.insertSelective : ==> Preparing: INSERT INTO t_test_a ( ID,NAME ) VALUES(?,? )
[http-nio-9902-exec-5] c.s.w.m.m.t.d.T.insertSelective : ==> Parameters: 1655001437939(String), p4xfy8(String)
[http-nio-9902-exec-5] c.s.w.m.m.t.d.T.insertSelective : <== Updates: 1
[http-nio-9902-exec-5] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
[http-nio-9902-exec-5] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@7fec11ca]
[http-nio-9902-exec-5] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@7fec11ca] after transaction
[http-nio-9902-exec-5] c.s.w.m.c.p.context.CurrentUserContext : CurrentUserContext remove CurrentUserVO...
[http-nio-9902-exec-5] c.s.w.m.f.a.CurrentUserContextAspect : CurrentUserContextAspect doAfterThrowing CurrentUserContext.remove()...
[http-nio-9902-exec-5] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler com.guzt.main.framework.exception.GlobalExceptionHandler#handleBusinessException(BusinessException)
[http-nio-9902-exec-5] c.s.w.m.f.e.GlobalExceptionHandler : BusinessException -- errorCode:E5111 errorMsg:模擬數(shù)據(jù)庫操作異常,主鍵重復
[http-nio-9902-exec-5] m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/json, application/*+json, application/json, application/*+json, application/cbor]
[http-nio-9902-exec-5] m.m.a.RequestResponseBodyMethodProcessor : Writing [ResponseVO(code=-1, message=模擬數(shù)據(jù)庫操作異常,主鍵重復, data={"errorBody":"","bussinessCode":"E5111","extraMsg":""})]
[http-nio-9902-exec-5] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.guzt.starter.common.exception.BusinessException: errorCode=E5111, errorMsg=FAIL]
[http-nio-9902-exec-5] o.s.web.servlet.DispatcherServlet : Completed 200 OK
主要的步驟:
Creating new transaction
DataSourceTransactionManager 根據(jù) TransactionDefinition 創(chuàng)建 TransactionStatus 對象,準備開啟事務Acquired Connection for JDBC transaction
通過數(shù)據(jù)庫連接池 申領一個數(shù)據(jù)庫連接Switching JDBC Connection to manual commit
開啟事務,底層是通過JDBC執(zhí)行 SET autocommit = 0; Initiating transaction rollback
準備回滾事務,修改 TransactionStatus 狀態(tài)為回滾Rolling back JDBC transaction
回滾事務 ,底層是通過JDBC執(zhí)行 rollback; SET autocommit = 1;Releasing JDBC Connection
釋放數(shù)據(jù)庫連接,最后是關閉了數(shù)據(jù)庫連接,底層調(diào)用了 JDBC Connection.close()
Spring中的事務接口
上面日志中提到了 TransactionDefinition TransactionStatus 等接口,這里也順便總結(jié)一下Spring事務中重要的幾個接口。
| 接口 | 含義 | 說明 |
|---|---|---|
| PlatformTransactionManager | 事務管理器 | 各類數(shù)據(jù)庫操作框架自行實現(xiàn)該接口,例如 DataSourceTransactionManager(JDBC), JtaTransactionManager, HibernateTransactionManager |
| TransactionDefinition | 事務定義信息(隔離級別、傳播行為、超時、只讀、回滾規(guī)則) , 一般在@Transactional 中指定這些屬性 | 如果是注解方式的事務,Spring 通過 AnnotationTransactionAttributeSource.getTransactionAttribute(Method method, Class<?> targetClass) 創(chuàng)建,參數(shù)中的method就是開啟事務的業(yè)務方法 |
| TransactionStatus | 事務運行狀態(tài),包含是否已完成,是否只讀,是否有恢復點,是否只回滾等 | 事務管理器接口 PlatformTransactionManager 通過 getTransaction(TransactionDefinition definition) 方法來得到一個事務 |
至于具體功能流程,這里不討論,自行看源碼,多debug即可明白。
到底回滾還是不回滾
本次討論重點就是 Spring的事務管理中,遇到了程序異常到底會不會回滾?
簡明答案
執(zhí)行事務的方法如果感知到了異常則將回滾事務
什么是執(zhí)行事務的方法
一般方法上增加了 @Transactional 注解或 其他Spring事務支持的方式AOP代理了的方法
@Transactional
public void saveTestA(TestA entity) {
業(yè)務代碼
}
什么情況下異常被感知
被Spring事務AOP所代理的業(yè)務方法執(zhí)行時出現(xiàn)異常,且異常類在Spring事務回滾范圍內(nèi)的將被調(diào)用方法所感知。
默認Spring只對 unchecked Exception 進行回滾,一般手動設定全部異常(rollbackFor = Exception.class)
什么情況下異常不被感知
一般這是討論事務不生效的場景。
- 方法訪問修飾符非public
- 法拋出的異常不是spring的事務支持的異常
- 被 Try-Cache 捕獲且不再向外拋出例如下面的場景代碼:
@Transactional
public void saveTestA(TestA entity) {
try{
業(yè)務代碼
} catch (Exception e) {
logger.error("內(nèi)部消化異常,不往外拋", e);
}
}
注解所在的類沒有被Spring 事務AOP代理
這種場景問題最隱蔽,一般需要有經(jīng)驗或者多次debug才能發(fā)現(xiàn)
同一個類里面方法互相調(diào)用,一般建議采用 SpringUtil.getBean(this.getClass()).xxxx事務方法()
某些策略模式場景,需要將service對象放到一個 Map<String, Service>中 ,如果是自行放置,則對象必須是 代理對象而非 this對象,建議采用 SpringUtil.getBean(this.getClass()) 對象。
參考代碼:
public class WxinOrderTypeServiceImpl implements OrderTypeService {
@PostConstruct
public void init() {
ORDER_TYPE_SERVICE.put("orderTypeKey", SpringUtil.getBean(this.getClass());
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveOrder(Order order)
logger.info("微信訂單");
}
}
上面代碼意思是,將Spring代理的對象 WxinOrderTypeServiceImpl$Cjlibxxxx 放置到策略map ORDER_TYPE_SERVICE中去,如果用 this ( ORDER_TYPE_SERVICE.put("orderTypeKey",this)) 則事務不生效,因為this雖然也在Spring beanFactory中但沒有被事務AOP所代理,因此用this 會不生效。
備注: SpringUtil 其實就是實現(xiàn)了接口 ApplicationContextAware 獲得ApplicationContext,ApplicationContext 中有 getBean方法,當然SpringBoot 可以在業(yè)務類里面直接注入 ApplicationContext
@Autowired ApplicationContext applicationContext;
數(shù)據(jù)庫本身不支持事務
如果使用MySQL且存儲引擎是MyISAM,則事務是不起作用的
異常被感知后Spring做些什么
異常被感知后,Spring將會做回滾或更新TransactionStatus的狀態(tài)(doSetRollbackOnly).
一旦TransactionStatus 被打上了 RollbackOnly標志后,那么不管中間的業(yè)務代碼是什么 都會拋出異常進行全部事務回滾。
那么什么時候 不做回滾只更新 TransactionStatus 為 RollbackOnly?
參見文章一開始的業(yè)務代碼,同屬于一個事務中(Propagation.REQUIRED)的多個執(zhí)行事務方法(業(yè)務代碼中嵌套調(diào)用其他service方法),如果不是首次開啟事務的那個方法則都只會更新 TransactionStatus 為 RollbackOnly,事務的提交回滾由首次事務開啟的那個方法執(zhí)行
參見源碼 org.springframework.transaction.support.AbstractPlatformTransactionManager# processRollback
回滾程度是多少
事務回滾到哪一個程度,是全部嵌套調(diào)用的方法都回滾還是部分方法回滾,這里主要是由Spring的事務傳播機制功能控制。
| 分類 | 行為 | 說明 | 回滾程度 |
|---|---|---|---|
| 加入當前事務 | PROPAGATION_REQUIRED | 默認方式,如果當前存在事務,則加入該事務;如果當前沒有事務,則創(chuàng)建一個新的事務 | 全部回滾 |
| 加入當前事務 | PROPAGATION_SUPPORTS | 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續(xù)運行 | 全部回滾 |
| 加入當前事務 | PROPAGATION_MANDATORY | 如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常 | 全部回滾 |
| 不加入當前事務 | PROPAGATION_REQUIRES_NEW | 創(chuàng)建一個新的事務,則把當前事務掛起 | Java里面還是同一個線程,只新創(chuàng)建了另外一個數(shù)據(jù)庫連接開啟事務,如果新事務回滾且程序異常被當前事務方法感知,則當前事務方法也同樣回滾 |
| 不加入當前事務 | PROPAGATION_NOT_SUPPORTED | 以非事務方式運行,如果當前存在事務,則把當前事務掛起 | Java里面還是同一個線程 如果程序異常被當前事務方法感知,則當前事務方法也同樣回滾 |
| 不加入當前事務 | PROPAGATION_NEVER | 以非事務方式運行,如果當前存在事務,則拋出異常 | 拋出異常,全部回滾 |
| 嵌套當前事務 | PROPAGATION_NESTED | 如果當前存在事務,則創(chuàng)建一個事務嵌套在當前事務中運行;如果當前沒有事務,則該取值等價于 PROPAGATION_REQUIRED | 只回滾自己 底層采用數(shù)據(jù)庫SavePoint功能 |
到此這篇關于Spring事務管理中的異?;貪L是什么的文章就介紹到這了,更多相關Spring異?;貪L內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JAVA 數(shù)據(jù)結(jié)構(gòu)鏈表操作循環(huán)鏈表
這篇文章主要介紹了JAVA 數(shù)據(jù)結(jié)構(gòu)鏈表操作循環(huán)鏈表的相關資料,需要的朋友可以參考下2016-10-10
Java web Hibernate如何與數(shù)據(jù)庫鏈接
這篇文章主要介紹了Java web Hibernate如何與數(shù)據(jù)庫鏈接,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06
SpringCloud如何創(chuàng)建一個服務提供者provider
這篇文章主要介紹了SpringCloud如何創(chuàng)建一個服務提供者provider,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-07-07
Spring Security實現(xiàn)自動登陸功能示例
自動登錄在很多網(wǎng)站和APP上都能用的到,解決了用戶每次輸入賬號密碼的麻煩。本文就使用Spring Security實現(xiàn)自動登陸功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11

