Spring @Transactional 宣告式事務揭祕

__HelloWorld__發表於2018-08-23

本章,我們將深入探討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:資料庫事務

https://dzone.com/articles/how-does-spring-transactional

相關文章