Spring @Transactional 宣告式事務揭祕
本章,我們將深入探討Spring事務管理,為你揭祕@Transactional註解底層工作原理。
JPA和事務管理
首先,我們要知道,JPA本身不提供任何宣告式事務管理。當我們在任意依賴注入容器外使用JPA時,需要由開發人員以程式設計方式來處理事務。
UserTransaction utx = entityManager.getTransaction();
try {
utx.begin();
businessLogic();
utx.commit();
} catch(Exception ex) {
utx.rollback();
throw ex;
}
這種管理事務的方式使得程式碼中的事務範圍非常清晰,但它有幾個明顯不足:
- 程式碼重複且容易出錯
- 任意一個微小的錯誤都都可能會產生很大的影響
- 錯誤很難除錯和重現
- 降低了程式碼的可讀性
- 如果此方法呼叫另一個事務方法如何處理事務?
使用Spring @Transactional註解
如果我們使用@Transactional註解,那麼上面的程式碼就可以簡化為:
@Transactional
public void businessLogic() {
... use entity manager inside a transaction ...
}
這樣以來,通過@Transactional註解使得程式碼更方便和可讀,這也是目前Spring中事務處理的推薦做法。
通過使用 @Transactional,許多事務相關的都可以被自動處理,例如:事務傳播等等。例如,如果businessLogic()方法呼叫另一個事務方法 ,該方法將根據事務傳播配置確定是否加入當前正在進行的事務。
然而,萬事皆有兩面性,@Transactional的強大機制為我們的事務管理提供了便利,然而正是這種強大機制隱藏了內部實現細節,進而使得當問題發生時,我們無法很好的去跟蹤除錯。
所以,我們要探究@Transactional背後的機制。
要正確理解@Transactional,我們首先要理解掌握以下兩個不同的概念,每一個概念都有自己的作用範圍和生命週期:
- Persistence Context:持久化上下文
- Database Transaction:資料庫事務
@Transactional註解本身定義的是一個資料庫事務的作用範圍,而資料庫事務則是作用在一個特定的持久化上下文(Persistence Context)中。
當我們使用Hibernate作為持久化方案時,JPA中的持久化上下文(Persistence Context)是Entity Manager(實體管理器),其由Hibernate Session內部實現。
持久化上下文(Persistence Context)本身只是一個同步器物件,其用以跟蹤記錄一組有限的Java物件的狀態,並確保對這些物件的更改最終被保留回資料庫。
這與一個資料庫事務是完全不同的概念,一個EntityManager(實體管理器)通常被用於多個資料庫事務。
EntityManager何時跨越多個資料庫事務?
最常見的情況是,當應用程式使用Open Session In View模式來處理延遲初始化異常時。在這種情況下,在檢視層中的多個查詢通常包含各自單獨的資料庫事務中,但這些資料庫事務都是由同一個EntityManager(實體管理器)所建立。
另一種情況則是開發人員將持久化上下文標記為PersistenceContextType.EXTENDED,這也就意味著它可以在多個請求中存在作用。
如何定義Entity Manager與Transaction關係?
這實際上是每個應用開發人員的選擇,但使用JPA Entity Manager 最常見的方式是
“Entity Manager per application transaction”模式(一個應用事務對應一個實體管理器EntityManager)。這是最常見的注入Entity Manager方法:
@PersistenceContext
private EntityManager em;
這裡,我們預設使用“Entity Manager per transaction”模式,即一個事務對應一個EntityManager模式。在此模式下,如果我們在@Transactional 方法中使用此EntityManager,則該方法將在單個資料庫事務中執行。
@PersistenceContext如何工作?
一個明顯的問題,考慮到EntityManager生命週期如此短暫,並且在每一個應用請求中又包含諸多EntityManager例項,那麼@PersistenceContext是如何在應用容器啟動時只注入到EntityManager一次呢?
答案是它不能:EntityManager只是一個介面,實際注入的是一個持久化上下文代理(context aware proxy:具體實現類SharedEntityManagerInvocationHandler),由其在執行時負責關聯到具體的實體管理器例項。
@Transactional如何工作
要想確保宣告式事務正確工作,僅僅靠實現了EntityManager介面的持久化上下文(persistence context proxy )代理並不行,這裡還需要介紹另外三個單獨的元件:
- EntityManager Proxy (實體管理器代理本身)
- Transactional Aspect(事務切面)
- Transaction Manager(事務管理器)
接下來,讓我們看下它們之間如何互動。
Transactional Aspect:事務切面
Transactional Aspect是一個”環繞”切面,具體實現類TransactionInterceptor,Transactional Aspect主要有兩個職責:
- 在“方法呼叫前”,切面提供一個hook point,用於確定要呼叫的業務方法加入當前正在進行的事務,還是新啟事務。
- 在“方法呼叫後”,由切面決定事務動作:提交,回滾或繼續執行。
在“方法呼叫前”,Transactional Aspect本身不包含任何決策邏輯,新啟事務的動作將委託給Transaction Manager。
Transaction Manager
Transaction Manager需要負責以下兩個邏輯:
- 是否需要建立新的Entity Manager例項
- 是否需要新啟新的資料庫事務
所有這些都會在Transactional Aspect ‘執行前’被確定,Transaction Manager將基於以下情況確認接下來的動作:
- 是否已有正在進行的事務
- 事務方法的傳播屬性
一旦確認需要建立新的資料庫事務,那麼Transaction Manager將執行以下動作:
- 建立新的Entity Manager例項
- 將建立的Entity Manager例項與當前執行緒繫結
- 從資料庫連線池中獲取資料庫連線
- 將獲取的資料庫連線與當前執行緒繫結
EntityManager例項和資料庫連線都會以ThreadLocal變數的方式與當前執行緒繫結。當事務執行時,他們會被儲存在當前執行緒中,並且由Transaction Manager負責清理。當然,如果需要獲取當前的執行緒的Entity Manager和資料庫連線,是需要通過EntityManager proxy來獲取。
EntityManager Proxy(Entity Manager 代理)
EntityManager代理是確保整個宣告事務機制正確執行的最後一個元件。假如,當業務方法呼叫entityManager.persist(),當前呼叫並不會直接呼叫EntityManager本身,而是呼叫EntityManager Proxy,由EntityManager Proxy負責從當前執行緒中獲取當前EntityManager例項物件(之前我們說的,EntityManager 會負責放置管理EntityManager的例項物件)。
例項驗證
接下來,我們來看下如何配置這三個元件,以使宣告式事務如何正確工作。
首先定義EntityManagerFactory,這樣EntityManager Proxy就可以通過@PersistenceContext實現注入。
@Configuration
public class EntityManagerFactoriesConfiguration {
@Autowired
private DataSource dataSource;
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emf = ...
emf.setDataSource(dataSource);
emf.setPackagesToScan(new String[] {"your.package"});
emf.setJpaVendorAdapter(
new HibernateJpaVendorAdapter());
return emf;
}
}
接下來,在@Transactional註解類中配置Transaction Manager以及應用Transactional Aspect
@Configuration
@EnableTransactionManagement
public class TransactionManagersConfig {
@Autowired
EntityManagerFactory emf;
@Autowired
private DataSource dataSource;
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
}
}
新增@EnableTransactionManagement註解的目的是告訴Spring,凡是帶有@Transactional註解的類都要被Transactional Aspect切面環繞。有了這個,@Transactional就可以正常工作了。
結論
Spring宣告式事務管理機制非常強大,但它容易誤用或錯誤配置。深入瞭解其工作機制對於後續我們解決問題時非常重要,但總結起來需要我們注意兩個概念:資料庫事務和持久化上下文:
- Persistence Context:持久化上下文
- Database Transaction:資料庫事務
相關文章
- Spring宣告式事務@Transactional使用Spring
- Spring中的AOP,以及宣告式事務 @Transactional無法攔截事務Spring
- Spring事務的介紹,以及基於註解@Transactional的宣告式事務Spring
- Spring Boot 揭祕與實戰(二) 資料儲存篇 - 宣告式事務管理Spring Boot
- spring事物配置,宣告式事務管理和基於@Transactional註解的使用Spring
- Spring-宣告式事務Spring
- 三 Spring 宣告式事務Spring
- Spring宣告式事務控制Spring
- spring宣告式事務管理配置Spring
- Spring的事務管理(二)宣告式事務管理Spring
- 深刻理解Spring宣告式事務Spring
- @Transactional spring 配置事務 注意事項Spring
- Spring中@Transactional事務使用陷阱Spring
- 五(二)、spring 宣告式事務xml配置SpringXML
- Spring宣告式事務控制原理之宣告式事務的重要元件在AOP中的應用Spring元件
- Spring的四種宣告式事務的配置-Hibernate事務Spring
- 【Spring註解】事務註解@TransactionalSpring
- Spring宣告式事務純xml模式回顧SpringXML模式
- JavaEE(12)Spring整合Mybaits、宣告式事務JavaSpringAI
- Spring boot +mybatis 實現宣告式事務管理Spring BootMyBatis
- spring宣告式事務無法關閉sessionSpringSession
- Spring筆記(4) - Spring的程式設計式事務和宣告式事務詳解Spring筆記程式設計
- 全面分析 Spring 的程式設計式事務管理及宣告式事務管理Spring程式設計
- Springboot資料庫事務處理——Spring宣告式事務Spring Boot資料庫
- Spring宣告式事務的兩種實現方式Spring
- 筆記53-Spring jdbcTemplate&宣告式事務筆記SpringJDBC
- Spring MVC + Mybatis + Spring Aop宣告式事務管理沒有作用SpringMVCMyBatis
- Spring宣告式事務注意點,以及不生效情況Spring
- Spring程式設計式和宣告式事務例項講解Spring程式設計
- Spring非同步Async和事務Transactional註解Spring非同步
- Spring宣告式事務管理出錯示例與解決之道Spring
- SpringMVC、MyBatis 宣告式事務管理SpringMVCMyBatis
- 宣告式事務能否和程式設計式事務巢狀使用?程式設計巢狀
- Java開啟事務(@Transactional)Java
- 《四 spring原始碼》spring的事務註解@Transactional 原理分析Spring原始碼
- 保護億萬資料安全,Spring有“宣告式事務”絕招Spring
- Spring中@Transactional事務回滾例項及原始碼Spring原始碼
- spring,mybatis事務管理配置與@Transactional註解使用[轉]SpringMyBatis