Spring事務管理器詳解

spring全家桶實戰案例 發佈 2023-03-27T07:13:03.122387+00:00

環境:Spring5.3.25理解Spring事務抽象Spring事務抽象的關鍵是事務策略的概念。事務策略是由TransactionManager定義的,特別是用於強制事務管理的org.springframework.transaction.

環境:Spring5.3.25


理解Spring事務抽象

Spring事務抽象的關鍵是事務策略的概念。事務策略是由transactionManager定義的,特別是用於強制事務管理的org.springframework.transaction.PlatformTransactionManager接口和用於響應式事務管理的org.springframework.transaction.ReactiveTransactionManager接口。下面的清單顯示了PlatformTransactionManager API的定義:

public interface PlatformTransactionManager extends TransactionManager {

  TransactionStatus getTransaction(TransactionDefinition definition) throws Transactionexception;

  void commit(TransactionStatus status) throws TransactionException;

  void rollback(TransactionStatus status) throws TransactionException;
}

這主要是一個服務提供者接口(SPI),儘管你可以從應用程式代碼中以編程方式使用它。因為PlatformTransactionManager是一個接口,所以可以根據需要輕鬆模擬或存根。它沒有綁定到查找策略,比如JNDI。PlatformTransactionManager實現的定義類似於Spring Framework IoC容器中的任何其他對象(或bean)。

同樣,為了與Spring的理念保持一致,可以由PlatformTransactionManager接口的任何方法拋出的TransactionException是未檢查的(也就是說,它擴展了java.lang. exception.RuntimeException類)。事務基礎結構失敗幾乎總是致命的。在極少數情況下,應用程式代碼實際上可以從事務失敗中恢復,應用程式開發人員仍然可以選擇捕獲和處理TransactionException。不過對於開發人員並沒有強制這樣做。

getTransaction(TransactionDefinition)方法返回一個TransactionStatus對象,這取決於TransactionDefinition參數。如果當前調用堆棧中存在匹配的事務,則返回的TransactionStatus可以表示一個新的事務,也可以表示一個現有的事務。後一種情況的含義是,與Java EE事務上下文一樣,TransactionStatus與執行線程相關聯。

從Spring Framework 5.2開始,spring還為使用響應式類型或Kotlin協程的響應式應用程式提供了事務管理抽象。下面的清單顯示了org.springframework.transaction.ReactiveTransactionManager定義的事務策略:

public interface ReactiveTransactionManager extends TransactionManager {

  Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;

  Mono<Void> commit(ReactiveTransaction status) throws TransactionException;

  Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

TransactionDefinition接口具體說明:

Propagation(傳播特性):通常,事務範圍內的所有代碼都在該事務中運行。但是,如果在事務上下文已經存在時運行事務方法,則可以指定該行為。例如,代碼可以繼續在現有事務中運行(常見情況),也可以暫停現有事務並創建新事務。

Isolation(隔離性):該事務與其他事務的工作隔離的程度。例如,這個事務能看到其他事務未提交的寫嗎?

Timeout(超時時間):該事務在超時和被底層事務基礎結構自動回滾之前運行的時間。

Read-Only(只讀狀態):當代碼讀取但不修改數據時,可以使用只讀事務。在某些情況下,例如使用Hibernate時,只讀事務可能是一種有用的優化。

這些設置反映了標準的事務概念。如有必要,請參考討論事務隔離級別和其他核心事務概念的資源。理解這些概念對於使用Spring框架或任何事務管理解決方案都是至關重要的。

TransactionStatus接口為事務代碼提供了一種控制事務執行和查詢事務狀態的簡單方法。這些概念應該很熟悉,因為它們對所有事務api都很常見。下面的清單顯示了TransactionStatus接口:

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

  @Override
  boolean isNewTransaction();

  boolean hasSavepoint();

  @Override
  void setRollbackOnly();

  @Override
  boolean isRollbackOnly();

  void flush();

  @Override
  boolean isCompleted();
}

無論在Spring中選擇聲明式事務管理還是編程式事務管理,定義正確的TransactionManager實現都是絕對必要的。通常通過依賴注入來定義此實現。

TransactionManager實現通常需要了解它們工作的環境:jdbc、JTA、Hibernate等等。下面的示例展示了如何定義本地PlatformTransactionManager實現(在本例中,使用普通JDBC)。

首先,你的先定義一個數據源:

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="jdbcUrl" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>

Java Config:

@Bean
public DataSource dataSource() {
  HikariDataSource dataSource = new HikariDataSource() ;
  dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true") ;
  dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver") ;
  dataSource.setUsername("root") ;
  dataSource.setPassword("root") ;
  return dataSource ;
}

相關的PlatformTransactionManager bean定義隨後具有對DataSource定義的引用。它應該類似於下面的例子:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

Java Config:

@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
  DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource);
  return txManager;
}

Hibernate事務配置

還可以輕鬆地使用hibernate本地事務,如以下示例所示。在這種情況下,需要定義一個Hibernate LocalsessionFactoryBean,應用程式代碼可以使用它來獲取Hibernate會話實例。

DataSource bean定義類似於前面顯示的本地JDBC示例

本例中的txManager bean屬於HibernateTransactionManager類型。就像DataSourceTransactionManager需要對DataSource的引用一樣,HibernateTransactionManager也需要對SessionFactory的引用。下面的例子聲明了sessionFactory和txManager bean:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="mappingResources">
    <list>
      <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <value>
      hibernate.dialect=${hibernate.dialect}
    </value>
  </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory"/>
</bean>

LocalSessionFactoryBean是個FactoryBean,同時實現了InitializingBean接口,所以在當前類初始化的時候,會調用afterPropertiesSet方法,該方法中會初始化SessionFactory對象。

將資源與事務同步

現在應該清楚了如何創建不同的事務管理器,以及如何將它們連結到需要同步到事務的相關資源(例如,DataSourceTransactionManager到JDBC DataSource,Hibernate TransactionManager到Hibernate SessionFactory,等等)。本節描述了應用程式代碼(直接或間接地,通過使用JDBC、Hibernate或JPA等持久性API)如何確保正確創建、重用和清理這些資源。本節還討論了如何(可選地)通過相關的TransactionManager觸發事務同步。

  • 高級同步方法

首選方法是使用Spring的最高級別基於模板的持久性集成API,或者使用具有事務感知工廠bean或代理的本地ORM API來管理本地資源工廠。這些事務感知解決方案在內部處理資源的創建和重用、清理、資源的可選事務同步以及異常映射。因此,用戶數據訪問代碼不必處理這些任務。通常,使用本機ORM API,或者通過使用JdbcTemplate採用模板方法進行JDBC訪問。

  • 低級同步方法

DataSourceUtils(用於JDBC)、EntityManagerFactoryUtils(適用於JPA)、SessionFactoryUtil(適用於Hibernate)等類存在於較低級別。當您希望應用程式代碼直接處理本機持久性API的資源類型時,可以使用這些類來確保獲得正確的Spring Framework託管實例,同步事務(可選),並將過程中發生的異常正確映射到一致的API。

例如,在JDBC的情況下,你可以使用Spring的org.springframework.jdbc.datasource.DataSourceUtils類,而不是在數據源上直接調用getConnection()方法,如下所示:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果現有事務已經有一個同步(連結)到它的連接,則返回該實例。否則,方法調用將觸發新連接的創建,該連接(可選地)同步到任何現有事務,並可用於同一事務中的後續重用。如前所述,任何SQLException都被包裝在Spring框架CannotGetJdbcConnectionException中,這是Spring框架中未檢查的DataAccessException類型的層次結構之一。這種方法提供了比從SQLException輕鬆獲得的更多信息,並確保了跨資料庫、甚至跨不同持久性技術的可移植性。

這種方法在沒有Spring事務管理的情況下也可以工作(事務同步是可選的),因此無論是否使用Spring進行事務管理,都可以使用它。

當然,一旦你使用了Spring的JDBC支持、JPA支持或Hibernate支持,你通常不喜歡使用DataSourceUtils或其他輔助類,因為你更喜歡通過Spring抽象工作而不是直接使用相關的api。例如,如果使用Spring JdbcTemplate或jdbc。對象包來簡化你的JDBC使用,正確的連接檢索發生在幕後,你不需要編寫任何特殊的代碼。

  • TransactionAwareDataSourceProxy

在最底層存在TransactionAwareDataSourceProxy類。這是目標DataSource的代理,它封裝目標DataSource以添加對Spring託管事務的感知。

除非必須調用現有代碼並傳遞標準JDBC DataSource接口實現,否則幾乎你不需要或不想使用這個類。在這種情況下,這段代碼可能是可用的,但參與了spring管理的事務。你可以使用前面提到的高級抽象來編寫新代碼。

聲明式事務管理

Spring Framework的聲明式事務管理是通過Spring面向方面編程(AOP)實現的。然而,由於事務方面的代碼是隨Spring Framework一起提供的,並且可以以樣板的方式使用,因此通常不需要理解AOP概念就可以有效地使用這些代碼。

僅僅告訴你使用@Transactional註解注釋的類,將@EnableTransactionManagement添加到你的配置中,並期望你理解它是如何工作的,這是不夠的。為了加深理解,本節將在與事務相關的問題上下文中解釋Spring框架的聲明式事務基礎結構的內部工作原理。

關於Spring Framework的聲明性事務支持,需要掌握的最重要的概念是,這種支持是通過AOP代理啟用的,並且事務Advice是由元數據驅動的(目前是基於XML或注釋的)。AOP與事務元數據的組合產生了一個AOP代理,該代理使用TransactionInterceptor和適當的TransactionManager實現來圍繞方法調用驅動事務。

Spring Framework的TransactionInterceptor為命令式和反應式編程模型提供事務管理。攔截器通過檢查方法返回類型來檢測所需的事務管理風格。返迴響應式類型(如Publisher或Kotlin Flow(或其子類型))的方法符合響應式事務管理的條件。包括void在內的所有其他返回類型都使用代碼路徑進行強制事務管理。

事務管理會影響所需的事務管理器。強制事務需要PlatformTransactionManager,而響應事務使用ReactiveTransactionManager實現。

@Transactional通常使用PlatformTransactionManager管理的線程綁定事務,將事務暴露給當前執行線程中的所有數據訪問操作。注意:這不會傳播到方法中新啟動的線程。

由ReactiveTransactionManager管理的反應事務使用Reactor上下文而不是線程本地屬性。因此,所有參與的數據訪問操作都需要在同一反應管道中的同一Reactor上下文中執行。

下圖顯示了在事務代理上調用方法的概念視圖:

  • 聲明性事務實現示例

考慮以下接口及其附屬實現。本例使用Foo和Bar類作為占位符,這樣你就可以專注於事務的使用,而不必關注特定的域模型。就本例而言,DefaultFooService類在每個實現方法的主體中拋出UnsupportedOperationException實例是好的。該行為允許你查看正在創建的事務,然後回滾以響應UnsupportedOperationException實例。FooService接口如下所示:

package x.y.service;

public interface FooService {

  Foo getFoo(String fooName);

  Foo getFoo(String fooName, String barName);

  void insertFoo(Foo foo);

  void updateFoo(Foo foo);

}

接口實現:

package x.y.service;

public class DefaultFooService implements FooService {

  @Override
  public Foo getFoo(String fooName) {
    // ...
  }

  @Override
  public Foo getFoo(String fooName, String barName) {
    // ...
  }

  @Override
  public void insertFoo(Foo foo) {
    // ...
  }

  @Override
  public void updateFoo(Foo foo) {
    // ...
  }
}

以FooService接口的前兩個方法getFoo(String)和getFoo為例配置事務攔截。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- 事務通知配置->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 事務語義 -->
        <tx:attributes>
            <!-- 所有以get開頭的方法的事務都是只讀的-->
            <tx:method name="get*" read-only="true"/>
            <!-- 其它事務都使用事務的默認行為 -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

<aop:config/>定義確保txAdvice bean定義的事務建議在程序中的適當點運行。首先,定義一個切入點,該切入點與FooService接口(fooServiceOperation)中定義的任何操作的執行相匹配。然後使用advisor將切入點與txAdvice關聯起來。結果表明,在執行fooServiceOperation時,運行由txAdvice定義的通知。

一個常見的要求是使整個服務層具有事務性。做到這一點的最佳方法是更改切入點表達式以匹配服務層中的任何操作。以下示例顯示了如何執行此操作:

<aop:config>
  <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
  • 回滾聲明性事務

上面介紹了如何在應用程式中以聲明方式為類(通常是服務層類)指定事務設置的基礎知識。本節描述如何在XML配置中以簡單的聲明式方式控制事務的回滾。

向Spring框架的事務基礎設施指示事務的工作要回滾的推薦方法是從當前在事務上下文中執行的代碼拋出異常。Spring框架的事務基礎結構代碼在調用堆棧中彈出氣泡時捕獲任何未處理的異常,並確定是否將事務標記為回滾。

在默認配置中,Spring框架的事務基礎結構代碼僅在運行時未檢查異常的情況下將事務標記為回滾。也就是說,當拋出的異常是RuntimeException的實例或子類時。(默認情況下,錯誤實例也會導致回滾)。從事務方法拋出的已檢查異常不會導致默認配置中的回滾。

你可以通過指定回滾規則,準確地配置哪些Exception類型標記要回滾的事務,包括已檢查的異常。

回滾規則

回滾規則確定在拋出給定異常時是否應該回滾事務,這些規則基於模式。模式可以是完全限定類名,也可以是異常類型(必須是Throwable的子類)的完全限定類名的子字符串,目前不支持通配符。例如,"javax.servlet.ServletException"或"ServletException"將匹配javax.servlet.ServletException及其子類。

可以通過Rollback-for和no-rollback-for屬性在XML中配置回滾規則,這些屬性允許將模式指定為字符串。當使用@Transactional時,可以通過rollbackFor/noRollbackFor和rollbackForClassName/noRollbackForClassName屬性來配置回滾規則,這些屬性允許分別將模式指定為類引用或字符串。當異常類型被指定為類引用時,其全限定名將用作模式。因此,@Transactional(rollbackFor = example.CustomException.class)等價於@Transactional(rollbackForClassName = "example.CustomException")。

以下XML片段演示了如何通過回滾for屬性提供異常模式,為已檢查的、特定於應用程式的異常類型配置回滾:

<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
  <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
  <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

如果不希望在引發異常時回滾事務,也可以指定「無回滾」規則。下面的示例告訴Spring Framework的事務基礎結構即使面對未處理的InstrumentNotFoundException也要提交附帶事務:

<tx:advice id="txAdvice">
  <tx:attributes>
  <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
  <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

當Spring Framework的事務基礎設施捕捉到異常並參考配置的回滾規則以確定是否將事務標記為回滾時,最強的匹配規則獲勝。因此,在以下配置的情況下,InstrumentNotFoundException以外的任何異常都會導致伴隨事務的回滾:

<tx:advice id="txAdvice">
  <tx:attributes>
  <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
  </tx:attributes>
</tx:advice>

你還可以通過編程方式指示所需的回滾。儘管這個過程很簡單,但它具有很強的侵入性,並將您的代碼緊密地耦合到Spring Framework的事務基礎設施。以下示例顯示了如何以編程方式指示所需的回滾:

public void resolvePosition() {
  try {
    // some business logic...
    } catch (NoProductInStockException ex) {
      // 調用setRollbackOnly進行回滾設置
      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
}

完畢!!!

SpringBoot對Spring MVC都做了哪些事?(一)
SpringBoot對Spring MVC都做了哪些事?(二)
SpringBoot對Spring MVC都做了哪些事?(三)
SpringBoot對Spring MVC都做了哪些事?(四)
Spring Retry重試框架的應用
spring data jpa 高級應用

關鍵字: