12.Spring之事務底層源碼解析

@EnableTransactionManagement工作原理

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

  1. AutoProxyRegistrar
  2. ProxyTransactionManagementConfiguration

AutoProxyRegistrar主要的作用是向Spring容器中註冊了一個InfrastructureAdvisorAutoProxyCreator的Bean。

而InfrastructureAdvisorAutoProxyCreator繼承了AbstractAdvisorAutoProxyCreator,所以這個類的主要作用就是開啓自動代理的作用,也就是一個BeanPostProcessor,會在初始化後步驟中去尋找Advisor類型的Bean,並判斷當前某個Bean是否有匹配的Advisor,是否需要利用動態代理產生一個代理對象。

ProxyTransactionManagementConfiguration是一個配置類,它又定義了另外三個bean:

  1. BeanFactoryTransactionAttributeSourceAdvisor:一個Advisor
  2. AnnotationTransactionAttributeSource:相當於BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut
  3. 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()方法,執行基本流程爲:

  1. 利用所配置的PlatformTransactionManager事務管理器新建一個數據庫連接
  2. 修改數據庫連接的autocommit爲false
  3. 執行MethodInvocation.proceed()方法,簡單理解就是執行業務方法,其中就會執行sql
  4. 如果沒有拋異常,則提交
  5. 如果拋了異常,則回滾

Spring事務詳細執行流程

Spring事務執行流程圖:https://www.processon.com/view/link/5fab6edf1e0853569633cc06

Spring事務傳播機制

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

  1. a()和b()方法中的所有sql需要在同一個事務中嗎?
  2. a()和b()方法需要單獨的事務嗎?
  3. a()需要在事務中執行,b()還需要在事務中執行嗎?
  4. 等等情況...

所以,這就要求Spring事務能支持上面各種場景,這就是Spring事務傳播機制的由來。那Spring事務傳播機制是如何實現的呢?

先來看上述幾種場景中的一種情況,a()在一個事務中執行,調用b()方法時需要新開一個事務執行:

  1. 首先,代理對象執行a()方法前,先利用事務管理器新建一個數據庫連接a
  2. 將數據庫連接a的autocommit改爲false
  3. 把數據庫連接a設置到ThreadLocal中
  4. 執行a()方法中的sql
  5. 執行a()方法過程中,調用了b()方法(注意用代理對象調用b()方法)
    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中
  1. 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,表示當前如果沒有事務則新建一個事務,如果有事務則在當前事務中執行。

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接conn
  2. 設置conn的autocommit爲false
  3. 執行test方法中的sql
  4. 執行a方法中的sql
  5. 執行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
	}
}

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接conn
  2. 設置conn的autocommit爲false
  3. 執行test方法中的sql
  4. 執行a方法中的sql
  5. 拋出異常
  6. 執行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;
	}
}

所以上面這種情況的執行流程如下:

  1. 新建一個數據庫連接conn
  2. 設置conn的autocommit爲false
  3. 執行test方法中的sql
  4. 執行a方法中的sql
  5. 拋出異常
  6. 執行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;
	}
}

所以上面這種情況的執行流程如下:

  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");
	}


}

  

本系列文章來自圖靈學院周瑜老師分享,本博客整理學習並搬運

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章