前言
隻有光頭才能變強。
文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y
Spring事務管理我相信大家都用得很多,但可能僅僅局限于一個
@Transactional
注解或者在
XML
中配置事務相關的東西。不管怎麼說,日常可能足夠我們去用了。但作為程式員,無論是為了面試還是說更好把控自己寫的代碼,還是應該得多多了解一下Spring事務的一些細節。
這裡我抛出幾個問題,看大家能不能瞬間答得上:
- 如果嵌套調用含有事務的方法,在Spring事務管理中,這屬于哪個知識點?
- 我們使用的架構可能是
或者是Hibernate/JPA
,都知道的底層是需要一個Mybatis
對象來幫我們執行操作的。要保證事務的完整性,我們需要多組資料庫操作要使用同一個session/connection
對象,而我們又知道Spring IOC所管理的對象預設都是單例的,這為啥我們在使用的時候不會引發線程安全問題呢?内部Spring到底幹了什麼?session/connection
- 人家所說的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打臉)
看了一下文檔,原來文檔有說明:
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
接下來我用圖來說明一下:
顯然地,我們拿到的是代理(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 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
回想一下當年我們學Mybaits的時候,是怎麼編寫Session工具類?
沒錯,用的就是ThreadLocal,同樣地,Spring也是用的ThreadLocal。
以下内容來源《精通 Spring4.x》
我們知道在一般情況下,隻有無狀态的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀态的“狀态性對象”采用ThreadLocal封裝,讓它們也成為線程安全的“狀态性對象”,是以,有狀态的Bean就能夠以singleton的方式在多線程中工作。
我們可以試着點一下進去TransactionSynchronizationManager中看一下:
五、啥是BPP?
BBP的全稱叫做:BeanPostProcessor,一般我們俗稱對象後處理器
- 簡單來說,通過BeanPostProcessor可以對我們的對象進行“加工處理”。
Spring管理Bean(或者說Bean的生命周期)也是一個常考的知識點,我在秋招也重新整理了一下步驟,因為比較重要,是以還是在這裡貼一下吧:
- ResouceLoader加載配置資訊
- BeanDefintionReader解析配置資訊,生成一個一個的BeanDefintion
- BeanDefintion由BeanDefintionRegistry管理起來
- BeanFactoryPostProcessor對配置資訊進行加工(也就是處理配置的資訊,一般通過PropertyPlaceholderConfigurer來實作)
- 執行個體化Bean
- 如果該Bean
了InstantiationAwareBean,則調用對應的方法配置/實作
- 使用BeanWarpper來完成對象之間的屬性配置(依賴)
-
Aware接口,則調用對應的方法配置/實作了
- 如果該Bean配置了BeanPostProcessor的before方法,則調用
- 如果該Bean配置了
或者實作InstantiationBean,則調用對應的方法init-method
- 如果該Bean配置了BeanPostProcessor的after方法,則調用
- 将對象放入到HashMap中
- 最後如果配置了destroy或者DisposableBean的方法,則執行銷毀操作
其中也有關于BPP圖檔:
5.1為什麼特意講BPP?
Spring AOP程式設計底層通過的是動态代理技術,在調用的時候肯定用的是代理對象。那麼Spring是怎麼做的呢?
我隻需要寫一個BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,對對象進行判斷,看他需不需要織入切面邏輯,如果需要,那我就根據這個對象,生成一個代理對象,然後傳回這個代理對象,那麼最終注入容器的,自然就是代理對象了。
Spring提供了BeanPostProcessor,就是讓我們可以對有需要的對象進行“加工處理”啊!
六、認識Spring事務幾個重要的接口
Spring事務可以分為兩種:
- 程式設計式事務(通過代碼的方式來實作事務)
- 聲明式事務(通過配置的方式來實作事務)
程式設計式事務在Spring實作相對簡單一些,而聲明式事務因為封裝了大量的東西(一般我們使用簡單,裡頭都非常複雜),是以聲明式事務實作要難得多。
在程式設計式事務中有以下幾個重要的了接口:
- TransactionDefinition:定義了Spring相容的事務屬性(比如事務隔離級别、事務傳播、事務逾時、是否隻讀狀态)
- TransactionStatus:代表了事務的具體運作狀态(擷取事務運作狀态的資訊,也可以通過該接口間接復原事務等操作)
- PlatformTransactionManager:事務管理器接口(定義了一組行為,具體實作交由不同的持久化架構來完成---類比JDBC)
在聲明式事務中,除了TransactionStatus和PlatformTransactionManager接口,還有幾個重要的接口:
- TransactionProxyFactoryBean:生成代理對象
- TransactionInterceptor:實作對象的攔截
- TransactionAttrubute:事務配置的資料
最後
本文主要講了Spring事務管理一些比較重要的知識點,當然在學習的過程中還看到其他的知識點,如果想要繼續學習的同學不妨通過下面給出的參考資料繼續閱讀。
參考資料:
- 那些年,我們一起追的Spring
- https://zhuanlan.zhihu.com/p/41961670
- 《精通Spring 4.x 企業應用開發實戰》
- 《Spring技術内幕》
樂于輸出幹貨的Java技術公衆号:Java3y。公衆号内有200多篇原創技術文章、海量視訊資源、精美腦圖,不妨來關注一下!
覺得我的文章寫得不錯,不妨點一下贊!
更多的文章可往:文章的目錄導航