首頁 > 軟體

Spring底層事務原理解析

2022-12-11 14:01:43

一、@EnableTransactionManagement工作原理

開啟Spring事務本質上就是增加了一個Advisor,但我們使用 @EnableTransactionManagement註解來開啟Spring事務是,該註解代理的功能就是向Spring容器中新增了兩個Bean:

(1)AutoProxyRegistrar
(2)ProxyTransactionManagementConfiguration

(1)AutoProxyRegistrar
主要的作用是向Spring容器中註冊了一個InfrastructureAdvisorAutoProxyCreator的Bean。
而InfrastructureAdvisorAutoProxyCreator繼承了AbstractAdvisorAutoProxyCreator,所以這個類的主要作用就是開啟自動代理的作用,也就是一個BeanPostProcessor,會在初始化後步驟中去尋找Advisor型別的Bean,並判斷當前某個Bean是否有匹配的Advisor,是否需要利用動態代理產生一個代理物件。

(2)ProxyTransactionManagementConfiguration是一個設定類,它又定義了另外三個bean:

bean定義
BeanFactoryTransactionAttributeSourceAdvisor一個Advisor。
AnnotationTransactionAttributeSource相當於BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut。就是用來判斷某個類上是否存在@Transactional註解,或者判斷某個方法上是否存在@Transactional註解的。
TransactionInterceptor相當於BeanFactoryTransactionAttributeSourceAdvisor中的Advice;就是代理邏輯,當某個類中存在@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事務的過程

1、資料庫:建立連線、開啟事務、進行sql操作、成功提交、失敗回滾
2、業務邏輯:準備工作(可以進行前置通知)、開啟事務、事務操作、成功提交(可以後置通知)、失敗回滾(異常通知)
spring的事務是由aop實現的,首先要生成具體的代理物件,然後按照aop流程執行具體
的操作邏輯,正常情況下要通過通知來完成核心功能,但是事務部署通過通知來實現的,
而是通過TransactionInterceptor來實現的,然後呼叫invoke來實現具體的邏輯。
步驟如下:
1、先做準備工作,解析各個方法上事務相關的屬性,根據具體的屬性來判斷是否開始新事務。

2、當需要開啟的時候獲取資料庫連線,關閉自動提交功能,開啟事務。

3、執行具體的sql邏輯操作,在操作的過程中如果執行失敗會通過 completeTransactionafterthrowing來完成事務的回滾操作,回滾的
具體邏輯是通過dorollback方法實現,實現時也要先獲取連線物件,
然後通過連線物件進行回滾(conn.rollback)。

4、如果執行成功,那麼通過completeTransactionafterrunning來完成事務的提交操作,
具體邏輯是通過docommit方法來實現,實現的時候也是先獲取連線,通過連線物件來
進行提交(conn.commit)。

5、最後事務執行完畢需要清除事務相關的事務資訊(cleanupTransactioninfo)。

四、Spring事務傳播機制

在開發過程中,經常會出現一個方法呼叫另外一個方法,那麼這裡就涉及到了多種場景,比如a()呼叫b():

a()和b()方法中的所有sql需要在同一個事務中嗎?a()和b()方法中的所有sql需要在同一個事務中嗎?a()需要在事務中執行,b()還需要在事務中執行嗎?或者其他情況
這種情況下就要求Spring事務能支援上面各種場景,這就是Spring事務傳播機制的由來。
那Spring事務傳播機制是如何實現的呢?

先描述其中一個場景中情況,a()在一個事務中執行,呼叫b()方法時需要新開一個事務執行:

代理物件執行a()方法前,先利用事務管理器新建一個資料庫連線a將資料庫連線a的autocommit改為false把資料庫連線a設定到ThreadLocal中執行a()方法中的sql執行a()方法過程中,呼叫了b()方法(注意用代理物件呼叫b()方法)a()方法正常執行完,則從ThreadLocal中拿到資料庫連線a進行提交

關於步驟5的一些詳細解釋:
1、代理物件執行b()方法前,判斷出來了當前執行緒中已經存在一個資料庫連線a了,表示當前執行緒其實已經擁有一個Spring事務了,則進行掛起
2、掛起就是把ThreadLocal中的資料庫連線a從ThreadLocal中移除,並放入一個掛起資源物件中
3、掛起完成後,再次利用事務管理器新建一個資料庫連線b
4、將資料庫連線b的autocommit改為false
5、把資料庫連線b設定到ThreadLocal中
6、執行b()方法中的sql
7、b()方法正常執行完,則從ThreadLocal中拿到資料庫連線b進行提交
8、提交之後會恢復所掛起的資料庫連線a,這裡的恢復,其實只是把在掛起資源物件中所儲存的資料庫連線a再次設定到ThreadLocal中

過程中最為重要的是:在執行某個方法時,判斷當前是否已經存在一個事務,就是判斷當前執行緒的ThreadLocal中是否存在一個資料庫連線物件,如果存在則表示已經存在一個事務了。

五、Spring事務傳播機制分類

在這裡面,以非事務方式執行,表示以非Spring事務執行,表示在執行這個方法時,Spring事務管理器不會去建立資料庫連線,執行sql時,由Mybatis或JdbcTemplate自己來建立資料庫連線來執行sql。

(1)案例分析、情況1

預設情況下傳播機制為REQUIRED,表示當前如果沒有事務則新建一個事務,如果有事務則在當前事務中執行。

@Component
public class UserService {
 	@Autowired
 	private UserService userService;

 	@Transactional
 	public void test() {
  		// test方法中的sql
  		userService.a();
 	}

 	@Transactional
 	public void a() {
  		// a方法中的sql
 	}
}

所以情況1的執行流程如下:

1、新建一個資料庫連線conn
2、設定conn的autocommit為false
3、執行test方法中的sql
4、執行a方法中的sql
5、執行conn的commit()方法進行提交

(2)案例分析、情況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
 	}
}

所以情況2的執行流程如下:

1、新建一個資料庫連線conn
2、設定conn的autocommit為false
3、執行test方法中的sql
4、執行a方法中的sql
5、丟擲異常
6、執行conn的rollback()方法進行回滾,所以兩個方法中的sql都會回滾掉

(3)案例分析、情況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;
 	}
}

所以情況3的執行流程如下:

1、新建一個資料庫連線conn
2、設定conn的autocommit為false
3、執行test方法中的sql
4、執行a方法中的sql
5、丟擲異常
6、執行conn的rollback()方法進行回滾,所以兩個方法中的sql都會回滾掉

(4)案例分析、情況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;
 	}
}

所以情況3的執行流程如下:

1、新建一個資料庫連線conn
2、設定conn的autocommit為false
3、執行test方法中的sql
4、又新建一個資料庫連線conn2
5、執行a方法中的sql
6、丟擲異常
7、執行conn2的rollback()方法進行回滾
8、繼續拋異常,對於test()方法而言,它會接收到一個異常,然後丟擲
9、執行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");
 }

}

到此這篇關於Spring底層事務原理的文章就介紹到這了,更多相關Spring底層事務原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com