一文帶你認識Spring事務

Java3y發表於2019-01-31

前言

只有光頭才能變強。

文字已收錄至我的GitHub倉庫,歡迎Star:github.com/ZhongFuChen…

Spring事務管理我相信大家都用得很多,但可能僅僅侷限於一個@Transactional註解或者在XML中配置事務相關的東西。不管怎麼說,日常可能足夠我們去用了。但作為程式設計師,無論是為了面試還是說更好把控自己寫的程式碼,還是應該得多多瞭解一下Spring事務的一些細節。

這裡我丟擲幾個問題,看大家能不能瞬間答得上:

  • 如果巢狀呼叫含有事務的方法,在Spring事務管理中,這屬於哪個知識點?
  • 我們使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底層是需要一個session/connection物件來幫我們執行操作的。要保證事務的完整性,我們需要多組資料庫操作要使用同一個session/connection物件,而我們又知道Spring IOC所管理的物件預設都是單例的,這為啥我們在使用的時候不會引發執行緒安全問題呢?內部Spring到底幹了什麼?
  • 人家所說的BPP又是啥東西?
  • Spring事務管理重要介面有哪幾個?

一、閱讀本文需要的基礎知識

閱讀這篇文章的同學我預設大家都對Spring事務相關知識有一定的瞭解了。(ps:如果不瞭解點解具體的文章去閱讀再回到這裡來哦)

我們都知道,Spring事務是Spring AOP的最佳實踐之一,所以說AOP入門基礎知識(簡單配置,使用)是需要先知道的。如果想更加全面瞭解AOP可以看這篇文章:AOP重要知識點(術語介紹、全面使用)。說到AOP就不能不說AOP底層原理:動態代理設計模式。到這裡,對AOP已經有一個基礎的認識了。於是我們就可以使用XML/註解方式來配置Spring事務管理

在IOC學習中,可以知道的是Spring中Bean的生命週期(引出BPP物件)並且IOC所管理的物件預設都是單例的:單例設計模式,單例物件如果有”狀態“(有成員變數),那麼多執行緒訪問這個單例物件,可能就造成執行緒不安全。那麼何為執行緒安全?,解決執行緒安全有很多方式,但其中有一種:讓每一個執行緒都擁有自己的一個變數:ThreadLocal

如果對我以上說的知識點不太瞭解的話,建議點選藍字進去學習一番。

二、兩個不靠譜直覺的例子

2.1第一個例子

之前朋友問了我一個例子:

在Service層丟擲Exception,在Controller層捕獲,那如果在Service中有異常,那會事務回滾嗎?



// Service方法
	
@Transactional
public Employee addEmployee() throws Exception {

    Employee employee = new Employee("3y", 23);
    employeeRepository.save(employee);
	// 假設這裡出了Exception
    int i = 1 / 0;

    return employee;
}

// Controller呼叫
@RequestMapping("/add")
public Employee addEmployee() {
    Employee employee = null;
    try {
        employee = employeeService.addEmployee();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return employee;

}

複製程式碼

第一反應:不會回滾吧。

  • 我當時是這樣想的:因為Service層已經丟擲了異常,由Controller捕獲。那是否回滾應該由Controller的catch程式碼塊中邏輯來決定,如果catch程式碼塊沒有回滾,那應該是不會回滾。

但朋友經過測試說,可以回滾阿。(pappapa打臉)

發生了執行時Exception,Spring事務管理自動回滾

看了一下文件,原來文件有說明:

By default checked exceptions do not result in the transactional interceptor marking the transaction for rollback and instances of RuntimeException and its subclasses do

結論:如果是編譯時異常不會自動回滾,如果是執行時異常,那會自動回滾

2.2第二個例子

第二個例子來源於知乎@柳樹文章,文末會給出相應的URL

我們都知道,帶有@Transactional註解所包圍的方法就能被Spring事務管理起來,那如果我在當前類下使用一個沒有事務的方法去呼叫一個有事務的方法,那我們這次呼叫會怎麼樣?是否會有事務呢?

用程式碼來描述一下:


// 沒有事務的方法去呼叫有事務的方法
public Employee addEmployee2Controller() throws Exception {

    return this.addEmployee();
}

@Transactional
public Employee addEmployee() throws Exception {

    employeeRepository.deleteAll();
    Employee employee = new Employee("3y", 23);

    // 模擬異常
    int i = 1 / 0;

    return employee;
}
複製程式碼

我第一直覺是:這跟Spring事務的傳播機制有關吧。

其實這跟Spring事務的傳播機制沒有關係,下面我講述一下:

  • Spring事務管理用的是AOP,AOP底層用的是動態代理。所以如果我們在類或者方法上標註註解@Transactional,那麼會生成一個代理物件

接下來我用圖來說明一下:

Spring會自動生成代理物件

顯然地,我們拿到的是代理(Proxy)物件,呼叫addEmployee2Controller()方法,而addEmployee2Controller()方法的邏輯是target.addEmployee(),呼叫回原始物件(target)的addEmployee()。所以這次的呼叫壓根就沒有事務存在,更談不上說Spring事務傳播機制了。

原有的資料:

原有的資料

測試結果:壓根就沒有事務的存在

沒有事務的存在
2.2.1再延伸一下

從上面的測試我們可以發現:如果是在本類中沒有事務的方法來呼叫標註註解@Transactional方法,最後的結論是沒有事務的。那如果我將這個標註註解的方法移到別的Service物件上,有沒有事務?


@Service
public class TestService {

    @Autowired
    private EmployeeRepository employeeRepository;
    
    @Transactional
    public Employee addEmployee() throws Exception {

        employeeRepository.deleteAll();

        Employee employee = new Employee("3y", 23);

        // 模擬異常
        int i = 1 / 0;

        return employee;
    }

}


@Service
public class EmployeeService {

    @Autowired
    private TestService testService;
    // 沒有事務的方法去呼叫別的類有事務的方法
    public Employee addEmployee2Controller() throws Exception {
        return testService.addEmployee();
    }
}


複製程式碼

測試結果:

丟擲了執行時異常,但我們的資料還是存在的!

因為我們用的是代理物件(Proxy)去呼叫addEmployee()方法,那就當然有事務了。

看完這兩個例子,有沒有覺得3y的直覺是真的水

三、Spring事務傳播機制

如果巢狀呼叫含有事務的方法,在Spring事務管理中,這屬於哪個知識點?

在當前含有事務方法內部呼叫其他的方法(無論該方法是否含有事務),這就屬於Spring事務傳播機制的知識點範疇了。

Spring事務基於Spring AOP,Spring AOP底層用的動態代理,動態代理有兩種方式:

  • 基於介面代理(JDK代理)
    • 基於介面代理,凡是類的方法非public修飾,或者用了static關鍵字修飾,那這些方法都不能被Spring AOP增強
  • 基於CGLib代理(子類代理)
    • 基於子類代理,凡是類的方法使用了private、static、final修飾,那這些方法都不能被Spring AOP增強

至於為啥以上的情況不能增強,用你們的腦瓜子想一下就知道了。

值得說明的是:那些不能被Spring AOP增強的方法並不是不能在事務環境下工作了。只要它們被外層的事務方法呼叫了,由於Spring事務管理的傳播級別,內部方法也可以工作在外部方法所啟動的事務上下文中

至於Spring事務傳播機制的幾個級別,我在這裡就不貼出來了。這裡只是再次解釋“啥情況才是屬於Spring事務傳播機制的範疇”。

四、多執行緒問題

我們使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底層是需要一個session/connection物件來幫我們執行操作的。要保證事務的完整性,我們需要多組資料庫操作要使用同一個session/connection物件,而我們又知道Spring IOC所管理的物件預設都是單例的,這為啥我們在使用的時候不會引發執行緒安全問題呢?內部Spring到底幹了什麼?

回想一下當年我們學Mybaits的時候,是怎麼編寫Session工具類

Mybatis工具類部分程式碼截圖

沒錯,用的就是ThreadLocal,同樣地,Spring也是用的ThreadLocal。

以下內容來源《精通 Spring4.x》

我們知道在一般情況下,只有無狀態的Bean才可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以宣告為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非執行緒安全狀態的“狀態性物件”採用ThreadLocal封裝,讓它們也成為執行緒安全的“狀態性物件”,因此,有狀態的Bean就能夠以singleton的方式在多執行緒中工作。

我們可以試著點一下進去TransactionSynchronizationManager中看一下:

全都是ThreadLocal

五、啥是BPP?

BBP的全稱叫做:BeanPostProcessor,一般我們俗稱物件後處理器

  • 簡單來說,通過BeanPostProcessor可以對我們的物件進行“加工處理”。

Spring管理Bean(或者說Bean的生命週期)也是一個常考的知識點,我在秋招也重新整理了一下步驟,因為比較重要,所以還是在這裡貼一下吧:

  1. ResouceLoader載入配置資訊
  2. BeanDefintionReader解析配置資訊,生成一個一個的BeanDefintion
  3. BeanDefintion由BeanDefintionRegistry管理起來
  4. BeanFactoryPostProcessor對配置資訊進行加工(也就是處理配置的資訊,一般通過PropertyPlaceholderConfigurer來實現)
  5. 例項化Bean
  6. 如果該Bean配置/實現了InstantiationAwareBean,則呼叫對應的方法
  7. 使用BeanWarpper來完成物件之間的屬性配置(依賴)
  8. 如果該Bean配置/實現了Aware介面,則呼叫對應的方法
  9. 如果該Bean配置了BeanPostProcessor的before方法,則呼叫
  10. 如果該Bean配置了init-method或者實現InstantiationBean,則呼叫對應的方法
  11. 如果該Bean配置了BeanPostProcessor的after方法,則呼叫
  12. 將物件放入到HashMap中
  13. 最後如果配置了destroy或者DisposableBean的方法,則執行銷燬操作
Application中Bean的宣告週期

其中也有關於BPP圖片:

BBP所在的位置

5.1為什麼特意講BPP?

Spring AOP程式設計底層通過的是動態代理技術,在呼叫的時候肯定用的是代理物件。那麼Spring是怎麼做的呢?

我只需要寫一個BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,對物件進行判斷,看他需不需要織入切面邏輯,如果需要,那我就根據這個物件,生成一個代理物件,然後返回這個代理物件,那麼最終注入容器的,自然就是代理物件了。

Spring提供了BeanPostProcessor,就是讓我們可以對有需要的物件進行“加工處理”啊!

六、認識Spring事務幾個重要的介面

Spring事務可以分為兩種:

  • 程式設計式事務(通過程式碼的方式來實現事務)
  • 宣告式事務(通過配置的方式來實現事務)

程式設計式事務在Spring實現相對簡單一些,而宣告式事務因為封裝了大量的東西(一般我們使用簡單,裡頭都非常複雜),所以宣告式事務實現要難得多。

在程式設計式事務中有以下幾個重要的了介面:

  • TransactionDefinition:定義了Spring相容的事務屬性(比如事務隔離級別、事務傳播、事務超時、是否只讀狀態)
  • TransactionStatus:代表了事務的具體執行狀態(獲取事務執行狀態的資訊,也可以通過該介面間接回滾事務等操作)
  • PlatformTransactionManager:事務管理器介面(定義了一組行為,具體實現交由不同的持久化框架來完成—類比JDBC)
PlatformTransactionManager解析

在宣告式事務中,除了TransactionStatus和PlatformTransactionManager介面,還有幾個重要的介面:

  • TransactionProxyFactoryBean:生成代理物件
  • TransactionInterceptor:實現物件的攔截
  • TransactionAttrubute:事務配置的資料

最後

本文主要講了Spring事務管理一些比較重要的知識點,當然在學習的過程中還看到其他的知識點,如果想要繼續學習的同學不妨通過下面給出的參考資料繼續閱讀。

參考資料:

樂於輸出乾貨的Java技術公眾號:Java3y。公眾號內有200多篇原創技術文章、海量視訊資源、精美腦圖,不妨來關注一下!

帥的人都關注了

覺得我的文章寫得不錯,不妨點一下

相關文章