一、@EnableTransactionManagement工作原理
開啟Spring事務本質上就是增加了一個Advisor,但我們使用@EnableTransactionManagement注解來開啟Spring事務是,該注解代理的功能就是向Spring容器中添加了兩個Bean:
- AutoProxyRegistrar
- ProxyTransactionManagementConfiguration
AutoProxyRegistrar主要的作用是向Spring容器中注冊了一個InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator繼承了AbstractAdvisorAutoProxyCreator,是以這個類的主要作用就是開啟自動代理的作用,也就是一個BeanPostProcessor,會在初始化後步驟中去尋找Advisor類型的Bean,并判斷目前某個Bean是否有比對的Advisor,是否需要利用動态代理産生一個代理對象。
ProxyTransactionManagementConfiguration是一個配置類,它又定義了另外三個bean:
- BeanFactoryTransactionAttributeSourceAdvisor:一個Advisor
- AnnotationTransactionAttributeSource:相當于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut
- TransactionInterceptor:相當于BeanFactoryTransactionAttributeSourceAdvisor中的Advice
AnnotationTransactionAttributeSource就是用來判斷某個類上是否存在@Transactional注解,或者判斷某個方法上是否存在@Transactional注解的。
TransactionInterceptor就是代理邏輯,當某個類中存在@Transactional注解時,到時就産生一個代理對象作為Bean,代理對象在執行某個方法時,最終就會進入到TransactionInterceptor的invoke()方法。
二、Spring事務基本執行原理
一個Bean在執行Bean的建立生命周期時,會經過InfrastructureAdvisorAutoProxyCreator的初始化後的方法,會判斷目前Bean對象是否和BeanFactoryTransactionAttributeSourceAdvisor比對,比對邏輯為判斷該Bean的類上是否存在@Transactional注解,或者類中的某個方法上是否存在@Transactional注解,如果存在則表示該Bean需要進行動态代理産生一個代理對象作為Bean對象。
該代理對象在執行某個方法時,會再次判斷目前執行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor比對,如果比對則執行該Advisor中的TransactionInterceptor的invoke()方法,執行基本流程為:
- 利用所配置的PlatformTransactionManager事務管理器建立一個資料庫連接配接
- 修改資料庫連接配接的autocommit為false
- 執行MethodInvocation.proceed()方法,簡單了解就是執行業務方法,其中就會執行sql
- 如果沒有抛異常,則送出
- 如果抛了異常,則復原
三、Spring事務詳細執行流程
Spring事務執行流程圖:https://www.processon.com/view/link/5fab6edf1e0853569633cc06
四、Spring事務傳播機制
在開發過程中,經常會出現一個方法調用另外一個方法,那麼這裡就涉及到了多種場景,比如a()調用b():
- a()和b()方法中的所有sql需要在同一個事務中嗎?
- a()和b()方法需要單獨的事務嗎?
- a()需要在事務中執行,b()還需要在事務中執行嗎?
- 等等情況…
是以,這就要求Spring事務能支援上面各種場景,這就是Spring事務傳播機制的由來。那Spring事務傳播機制是如何實作的呢?
先來看上述幾種場景中的一種情況,a()在一個事務中執行,調用b()方法時需要新開一個事務執行:
- 首先,代理對象執行a()方法前,先利用事務管理器建立一個資料庫連接配接a
- 将資料庫連接配接a的autocommit改為false
- 把資料庫連接配接a設定到ThreadLocal中
- 執行a()方法中的sql
- 執行a()方法過程中,調用了b()方法(注意用代理對象調用b()方法)
- 代理對象執行b()方法前,判斷出來了目前線程中已經存在一個資料庫連接配接a了,表示目前線程其實已經擁有一個Spring事務了,則進行挂起
- 挂起就是把ThreadLocal中的資料庫連接配接a從ThreadLocal中移除,并放入一個挂起資源對象中
- 挂起完成後,再次利用事務管理器建立一個資料庫連接配接b
- 将資料庫連接配接b的autocommit改為false
- 把資料庫連接配接b設定到ThreadLocal中
- 執行b()方法中的sql
- b()方法正常執行完,則從ThreadLocal中拿到資料庫連接配接b進行送出
- 送出之後會恢複所挂起的資料庫連接配接a,這裡的恢複,其實隻是把在挂起資源對象中所儲存的資料庫連接配接a再次設定到ThreadLocal中
- a()方法正常執行完,則從ThreadLocal中拿到資料庫連接配接a進行送出
這個過程中最為核心的是:在執行某個方法時,判斷目前是否已經存在一個事務,就是判斷目前線程的ThreadLocal中是否存在一個資料庫連接配接對象,如果存在則表示已經存在一個事務了。
五、Spring事務傳播機制分類
其中,以非事務方式運作,表示以非Spring事務運作,表示在執行這個方法時,Spring事務管理器不會去建立資料庫連接配接,執行sql時,由Mybatis或JdbcTemplate自己來建立資料庫連接配接來執行sql。
案例分析
情況1
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
}
@Transactional
public void a() {
// a方法中的sql
}
}
預設情況下傳播機制為REQUIRED,表示目前如果沒有事務則建立一個事務,如果有事務則在目前事務中執行。另外,搜尋公衆号Linux中文社群背景回複“指令行”,擷取一份驚喜禮包。
是以上面這種情況的執行流程如下:
- 建立一個資料庫連接配接conn
- 設定conn的autocommit為false
- 執行test方法中的sql
- 執行a方法中的sql
- 執行conn的commit()方法進行送出
情況2
假如是這種情況:
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
int result = 100/0;
}
@Transactional
public void a() {
// a方法中的sql
}
}
是以上面這種情況的執行流程如下:
- 建立一個資料庫連接配接conn
- 設定conn的autocommit為false
- 執行test方法中的sql
- 執行a方法中的sql
- 抛出異常
- 執行conn的rollback()方法進行復原,是以兩個方法中的sql都會復原掉
情況3
假如是這種情況:
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
}
@Transactional
public void a() {
// a方法中的sql
int result = 100/0;
}
}
是以上面這種情況的執行流程如下:
- 建立一個資料庫連接配接conn
- 設定conn的autocommit為false
- 執行test方法中的sql
- 執行a方法中的sql
- 抛出異常
- 執行conn的rollback()方法進行復原,是以兩個方法中的sql都會復原掉
情況4
如果是這種情況:
@Component
public class UserService {
@Autowired
private UserService userService;
@Transactional
public void test() {
// test方法中的sql
userService.a();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void a() {
// a方法中的sql
int result = 100/0;
}
}
牛逼啊!接私活必備的 N 個開源項目!趕快收藏
是以上面這種情況的執行流程如下:
- 建立一個資料庫連接配接conn
- 設定conn的autocommit為false
- 執行test方法中的sql
- 又建立一個資料庫連接配接conn2
- 執行a方法中的sql
- 抛出異常
- 執行conn2的rollback()方法進行復原
- 繼續抛異常,對于test()方法而言,它會接收到一個異常,然後抛出
- 執行conn的rollback()方法進行復原,最終還是兩個方法中的sql都復原了
六、Spring事務強制復原
正常情況下,a()調用b()方法時,如果b()方法抛了異常,但是在a()方法捕獲了,那麼a()的事務還是會正常送出的,但是有的時候,我們捕獲異常可能僅僅隻是不把異常資訊傳回給用戶端,而是為了傳回一些更友好的錯誤資訊,而這個時候,我們還是希望事務能復原的,那這個時候就得告訴Spring把目前事務復原掉,做法就是:
@Transactional
public void test(){
// 執行sql
try {
b();
} catch (Exception e) {
// 構造友好的錯誤資訊傳回
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
public void b() throws Exception {
throw new Exception();
}
七、TransactionSynchronization
Spring事務有可能會送出,復原、挂起、恢複,是以Spring事務提供了一種機制,可以讓程式員來監聽目前Spring事務所處于的狀态。
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Transactional
public void test(){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void suspend() {
System.out.println("test被挂起了");
}
@Override
public void resume() {
System.out.println("test被恢複了");
}
@Override
public void beforeCommit(boolean readOnly) {
System.out.println("test準備要送出了");
}
@Override
public void beforeCompletion() {
System.out.println("test準備要送出或復原了");
}
@Override
public void afterCommit() {
System.out.println("test送出成功了");
}
@Override
public void afterCompletion(int status) {
System.out.println("test送出或復原成功了");
}
});
jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
System.out.println("test");
userService.a();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void a(){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void suspend() {
System.out.println("a被挂起了");
}
@Override
public void resume() {
System.out.println("a被恢複了");
}
@Override
public void beforeCommit(boolean readOnly) {
System.out.println("a準備要送出了");
}
@Override
public void beforeCompletion() {
System.out.println("a準備要送出或復原了");
}
@Override
public void afterCommit() {
System.out.println("a送出成功了");
}
@Override
public void afterCompletion(int status) {
System.out.println("a送出或復原成功了");
}
});
jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");
System.out.println("a");
}
}
原文連結:https://mp.weixin.qq.com/s/_qKOogkCgCTucIwZtOBOQQ