天天看點

事務之五:Spring @Transactional工作原理

本文将深入研究Spring的事務管理。主要介紹

@Transactional

在底層是如何工作的。

JPA(Java Persistence API--java持久層)和事務管理

很重要的一點是JPA本身并不提供任何類型的聲明式事務管理。如果在依賴注入容器之外使用JPA,事務處理必須由開發人員程式設計實作。

UserTransaction utx = entityManager.getTransaction(); 
 
    try {
        utx.begin();
 
        businessLogic();
 
        utx.commit();
    } catch(Exception ex) {
        utx.rollback();
        throw ex;
    }      

這種方式的事務管理使事務範圍可以在代碼中很清晰地表達出來,但它有以下缺點:

  • 容易出現重複代碼和錯誤
  • 任何錯誤可能産生較大的影響
  • 錯誤難以調試和複現
  • 降低了代碼庫的可讀性
  • 如果該方法調用了其他的事務方法如何處理呢?

使用Spring @Transactional

使用Spring 

@Transactional

,上面的代碼就簡化為:

@Transactional
    public void businessLogic() {
        ... use entity manager inside a transaction ...
    }      

代碼更加簡潔,可讀性更好,也是目前Spring中事務處理的推薦方式。

通過使用

@Transactional

,事務傳播等很多重要方面可以自動處理。這種情況下如果

businessLogic()

調用了其他事務方法,該方法将根據選項确定如何加入正在運作事務。

這個強大機制的一個潛在缺點是它隐藏了底層的運作,當它不能正常工作時很難調試。

@Transactional

含義

關于

@Transactional

,關鍵點之一是要考慮兩個獨立的概念,它們都有各自的範圍和生命周期:

  • persistence context(持久化上下文)
  • database transaction(事務)

@Transactional

本身定義了單個事務的範圍。這個事務在persistence context的範圍内。

JPA中的持久化上下文是

EntityManager

,内部實作使用了Hibernate 

Session

(使用Hibernate作為持久化provider)。

持久化上下文僅僅是一個同步對象,它記錄了有限集合的Java對象的狀态,并且保證這些對象的變化最終持久化到資料庫。

這是與單個事務非常不同的概念。一個Entity Manager可以跨越多個事務使用,而且的确是這樣使用的。

EntityManager何時跨越多個事務?

最常見的情況是應用使用Open Session In View模式處理懶初始化異常時,之前的文章介紹過這種做法的優勢和劣勢。

這種情況下視圖層運作的多個查詢處于獨立的事務中,而不是單事務的業務邏輯,但這些查詢由相同的entity manager管理。

另一種情況是開發人員将持久化上下文标記為

PersistenceContextType.EXTENDED

,這表示它能夠響應多個請求。

如何定義EntityManager和Transaction之間的關系?

這由應用開發者來選擇,但是JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每個事務都有自己的實體管理器)模式。entity manager注入的常用方法是:

@PersistenceContext
    private EntityManager em;      

這裡預設為“Entity Manager per transaction”模式。這種模式下如果在

@Transactional

方法内部使用該Entity Manager,那麼該方法将在單一事務中運作。

@PersistenceContext如何工作?

随之而來的問題就是

@PersistenceContext

如何僅在容器啟動時注入entity manager,假定entity manager生命周期很短暫,而且每次請求需要多個entity manager。

答案是它不能:

EntityManager

是一個接口,注入到spring bean中的不是entity manager本身,而是在運作時代理具體entity manager的context aware proxy(上下文感覺代理)。

通常用于代理的具體類為

SharedEntityManagerInvocationHandler

,借助調試器可以确認這一點。

那麼@Transactional如何工作?

實作了

EntityManager

接口的持久化上下文代理并不是聲明式事務管理的唯一部分,事實上包含三個組成部分:

  • EntityManager Proxy本身
  • 事務的切面
  • 事務管理器

看一下這三部分以及它們之間的互相作用。

事務的切面

事務的切面是一個“around(環繞)”切面,在注解的業務方法前後都可以被調用。實作切面的具體類是

TransactionInterceptor

事務的切面有兩個主要職責:

  • 在’before’時,切面提供一個調用點,來決定被調用業務方法應該在正在進行事務的範圍内運作,還是開始一個新的獨立事務。
  • 在’after’時,切面需要确定事務被送出,復原或者繼續運作。

在’before’時,事務切面自身不包含任何決策邏輯,是否開始新事務的決策委派給事務管理器完成。

事務管理器

事務管理器需要解決下面兩個問題:

  • 新的Entity Manager是否應該被建立?
  • 是否應該開始新的事務?

這些需要事務切面’before’邏輯被調用時決定。事務管理器的決策基于以下兩點:

  • 事務是否正在進行
  • 事務方法的propagation屬性(比如

    REQUIRES_NEW

    總要開始新事務)

如果事務管理器确定要建立新事務,那麼将:

  • 建立一個新的entity manager
  • entity manager綁定到目前線程
  • 從資料庫連接配接池中擷取連接配接
  • 将連接配接綁定到目前線程

使用ThreadLocal變量将entity manager和資料庫連接配接都綁定到目前線程。

事務運作時他們存儲線上程中,當它們不再被使用時,事務管理器決定是否将他們清除。

程式的任何部分如果需要目前的entity manager和資料庫連接配接都可以從線程中擷取。

EntityManager proxy

EntityManager proxy(前面已經介紹過)就是謎題的最後一部分。當業務方法調用

entityManager.persist()

時,這不是由entity manager直接調用的。

而是業務方法調用代理,代理從線程擷取目前的entity manager,前面介紹過事務管理器将entity manager綁定到線程。

了解了

@Transactional

機制的各個部分,我們來看一下實作它的常用Spring配置。

整合三個部分

如何将三個部分組合起來使事務注解可以正确地發揮作用呢?首先定義entity manager工廠。

這樣就可以通過持久化上下文注解注入Entity Manager proxy。

@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

注解的類中應用事務的切面。

@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

就可以使用了。

總結

Spring聲明式事務管理機制非常強大,但它可能被誤用或者容易發生配置錯誤。

當這個機制不能正常工作或者未達到預期運作結果等問題出現時,了解它的内部工作情況是很有幫助的。

需要記住的最重要的一點是,要考慮到兩個概念:事務和持久化上下文,每個都有自己不可讀的明顯的生命周期。