這篇文章帶給大家的內容是關於Spring事務的深入解析(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
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
如果對我以上說的知識點不太了解的話,建議點擊藍字進去學習一番。
二、兩個不靠譜直覺的例子
之前朋友問了我一個例子:
在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
結論:如果是編譯時異常不會自動回滾,如果是執行時間異常,那會自動回滾!
第二個例子來自於知乎@柳樹文章,文末會給出對應的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事务传播机制了。
Spring事務的深入解析(附範例):
测试结果:压根就Spring事務的深入解析(附範例)
从上面的测试我们可以发现:如果是在本类中没有事务的方法来调用标注注解@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
对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?
回想一下当年我们学Mybaits的时候,是怎么编写Session工具类?
没错,用的就是ThreadLocal,同样地,Spring也是用的ThreadLocal。
以下内容来源《精通 Spring4.x》
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的“状态性对象”采用ThreadLocal封装,让它们也成为线程安全的“状态性对象”,因此,有状态的Bean就能够以singleton的方式在多线程中工作。
我们可以试着点一下进去TransactionSynchronizationManager中看一下:
BBP的全名為:BeanPostProcessor,一般我們俗稱物件後處理器
簡單來說,透過BeanPostProcessor可以對我們的物件進行“加工處理”。
Spring管理Bean(或說Bean的生命週期)也是一個常考的知識點,我在秋招也重新整理了一下步驟,因為比較重要,所以還是在這裡貼一下吧:
ResouceLoader加載配置信息
BeanDefintionReader解析配置信息,生成一個一個的BeanDefintion
BeanDefintion由BeanDefintionRegistry管理起來
設定/實作了InstantiationAwareBean,則呼叫對應的方法
配置/實作了 Aware接口,則呼叫對應的方法
init-method或實作InstantiationBean,則呼叫對應的方法
代理物件。那麼Spring是怎麼做的呢?
我只需要寫一個BPP,在postProcessBeforeInitialization或postProcessAfterInitialization方法中,對物件進行判斷,看他需不需要織入切面邏輯,如果需要,那我就根據這個對象,生成一個代理對象,然後返回這個代理對象,那麼最終注入容器的,自然就是代理對象了。Spring提供了BeanPostProcessor,就是讓我們可以對有需要的物件進行「
加工處理」啊!
六、認識Spring事務幾個重要的介面Spring交易可以分為兩種:交易屬性(例如事務隔離等級、事務傳播、交易逾時、是否唯讀狀態)
運行狀態(獲取事務運行狀態的信息,也可以透過此介面間接回溯事務等操作)
類比JDBC)
以上是Spring事務的深入解析(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!