深入瞭解Spring之事務

GitHub:https://github.com/JDawnF/learning_note

目錄

1、事務

2、事務的特性

3、列舉 Spring 支持的事務管理類型

3.1 聲明式事務

a.基於TransactionInterceptor的聲明式事務:

b.基於TransactionProxyFactoryBean的聲明式事務

c.基於命名空間的聲明式事務

d.基於標註(@Transactional)的聲明式事務

3.2 編程式事務

4、事務管理接口

4.1 Spring 事務如何和不同的數據持久層框架做集成

4.2 PlatformTransactionManager接口代碼如下:

4.3 TransactionDefinition接口介紹

事務屬性

4.4 TransactionStatus接口介紹


1、事務

事務就是對一系列的數據庫操作(比如插入多條數據)進行統一的提交或回滾操作,如果插入成功,那麼一起成功,如果中間有一條出現異常,那麼回滾之前的所有操作。

這樣可以防止出現髒數據,防止數據庫數據出現問題。

2、事務的特性

指的是 ACID ,如下圖所示:

  1. 原子性 Atomicity :一個事務(transaction)中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。即,事務不可分割、不可約簡。

  2. 一致性 Consistency :在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設約束觸發器級聯回滾等。

  3. 隔離性 Isolation :數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。

  4. 持久性 Durability :事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。

3、列舉 Spring 支持的事務管理類型

目前 Spring 提供兩種類型的事務管理:

  • 聲明式事務:基於AOP,通過使用註解或基於 XML 的配置事務,從而事務管理與業務代碼分離。

  • 編程式事務:通過編碼的方式實現事務管理,需要在代碼中顯式的調用事務的獲得、提交、回滾,提供極大的靈活性,但維護起來非常困難。

3.1 聲明式事務

    Spring的聲明式事務管理建立在AOP基礎上,其本質是在目標方法執行前進行攔截,在方法開始前創建一個事務,在執行完方法後根據執行情況提交或回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不用侵入業務代碼,只需要在配置文件中做相關的事物聲明就可將業務規則應用到業務邏輯中。和編程式事務相比,聲明式事務唯一的不足是智能作用到方法級別,無法做到像編程式事務那樣到代碼塊級別。

聲明式事務有四種方式:

a.基於TransactionInterceptor的聲明式事務;

b.基於TransactionProxyFactoryBean的聲明式事務;

c.基於命名空間的聲明式事務;

d.基於標註(@Transactional)的聲明式事務。

a.基於TransactionInterceptor的聲明式事務:

  TransactionInterceptor主要有兩個屬性,一個是transactionManager,用於指定一個事務管理器;另一個是transactionAttributes,通過鍵值對的方式指定相應方法的事物屬性,其中鍵值可以使用通配符。在config.xml配置文件中加入TransactionInterceptor配置: 

...
     <bean id="transactionInterceptor"
           class="org.springframework.transaction.interceptor.TransactionInterceptor">
         <property name="transactionManager" ref="transactionManager"/>
         <property name="transactionAttributes">
             <props>
                 <prop key="*">PROPAGATION_REQUIRED</prop>
             </props>
         </property>
     </bean>
     <bean id="userService"
           class="org.springframework.aop.framework.ProxyFactoryBean">
         <property name="target">
             <bean class="com.xiaofan.test.UserService" />
         </property>
         <property name="interceptorNames">
             <list>
                 <idref bean="transactionInterceptor"/>
             </list>
         </property>
     </bean>
 ...
 public class UserService {
     @Resource
     UserDAO userDAO;
     public void addUser3(User user) {
         userDAO.insert(user);
         Integer i = 1;
         if (i.equals(0)) {
         }
     }
 }

b.基於TransactionProxyFactoryBean的聲明式事務

  以上基於TransactionInterceptor的方式每個服務bean都需要配置一個ProxyFactoryBean,這會導致配置文件冗長,爲了緩解這個問題,Spring提供了基於TransactionProxyFactoryBean的聲明式事務配置方式。在config.xml配置文件中加入TransactionProxyFactoryBean配置:

 
...
     <bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
         <property name="target">
             <bean class="com.xiaofan.test.UserService" />
         </property>
         <property name="transactionManager" ref="transactionManager"/>
         <property name="transactionAttributes">
             <props>
                 <prop key="insert*">PROPAGATION_REQUIRED</prop>
                 <prop key="update*">PROPAGATION_REQUIRED</prop>
                 <prop key="*">PROPAGATION_REQUIRED</prop>
             </props>
         </property>
     </bean>
 ...

UserService服務代碼如上,不用變更。

c.基於命名空間的聲明式事務

  Spring 2.x引入了命名空間,加上命名空間的切點表達式支持,聲明式事務變的更加強大,藉助於切點表達式,可以不需要爲每個業務類創建一個代理。爲了使用動態代理,首先需要添加pom依賴:

...
         <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjweaver</artifactId>
             <version>1.7.4</version>
         </dependency>
 ...

config.xml文件添加如下配置:

<bean id="userService" class="com.xiaofan.test.UserService">
     </bean>
     <tx:advice id="userAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="*" propagation="REQUIRED"/>
         </tx:attributes>
     </tx:advice>
     <aop:config>
         <!--匹配模式,匹配對應的方法,參照上面的AOP-->
         <aop:pointcut id="userPointcut" expression="execution (* com.xiaofan.test.*.*(..))"/>
         <aop:advisor advice-ref="userAdvice" pointcut-ref="userPointcut"/>
     </aop:config>

UserService服務代碼如上,不用變更。

d.基於標註(@Transactional)的聲明式事務

  除了基於命名空間的事務配置方式外,Spring2.x還引入了基於註解的方式,主要涉及@Transactional註解,它可以作用於接口、接口方法、類和類的方法上,當做用於類上時,該類的所有public方法都有該類型的事務屬性,可被方法級事務覆蓋。在config.xml配置文件中加入註解識別配置:

<tx:annotation-driven transaction-manager="transactionManager"/>

 @Transactional註解應該被應用到public方法上,這是由AOP的本質決定的,如果應用在protected、private的方法上,事務將被忽略。UserService服務代碼如下:

public class UserService {
     @Resource
     UserDAO userDAO;
     @Transactional(propagation = Propagation.REQUIRED)
     public void addUser4(User user) {
         userDAO.insert(user);
         Integer i = 1;
         if (i.equals(0)) {
 ​
         }
     }
 }

@Transactional註解的完整屬性信息如下表:

基於命名空間和基於註解的事務聲明各有優缺點:基於命名空間的方式一個配置可以匹配多個方法,但配置較註解方式複雜;基於註解的方式需要在每個需要使用事務的方法或類上標註,但基於標註的方法學習成本更低。

3.2 編程式事務

一般的使用:

Spring配置文件config.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="
             http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
             http://www.springframework.org/schema/tx
                 http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
             http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context-3.0.xsd">
     <!-- 引入屬性文件 -->
     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
         <property name="locations">
             <list>
                 <value>classpath:config.properties</value>
             </list>
         </property>
     </bean>
     <!-- 配置數據源 -->
     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
         <property name="driverClassName">
             <value>${driver}</value>
         </property>
         <property name="url">
             <value>${url}</value>
         </property>
         <property name="username">
             <value>${username}</value>
         </property>
         <property name="password">
             <value>${password}</value>
         </property>
     </bean>
 ​
     <!-- 自動掃描了所有的mapper配置文件對應的mapper接口文件 -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
         <property name="basePackage" value="com.xiaofan.test" />
     </bean>
     <!-- 配置Mybatis的文件 -->
     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
         <property name="dataSource" ref="dataSource" />
         <property name="mapperLocations" value="classpath:user_mapper.xml"/>
         <property name="configLocation" value="classpath:mybatis_config.xml" />
     </bean>
     <!-- 配置JDBC事務管理器 -->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="dataSource" />
     </bean>
     <bean id="userService" class="com.xiaofan.test.UserService">
     </bean>
 </beans>

根據PlatformTransactionManager、TransactionDefinition和TransactionStatus三個接口,可以通過編程的方式來進行事務管理, TransactionDefinition實例用於定義一個事務,PlatformTransactionManager實例用語執行事務管理操作,TransactionStatus實例用於跟蹤事務的狀態。UserService服務中配置如下:

public class UserService {
     @Resource
     UserDAO userDAO;
     @Resource
     DataSource dataSource;
     @Resource
     PlatformTransactionManager transactionManager;
     public void addUser(User user) throws Exception {
         TransactionDefinition def = new DefaultTransactionDefinition();
         TransactionStatus status = transactionManager.getTransaction(def);
         try {
             // [1] 插入紀錄
             userDAO.insert(user);
             // [2] 範例拋出異常
             Integer i = null;
             if (i.equals(0)) {
 ​
             }
             transactionManager.commit(status);
         } catch (Exception e) {
             transactionManager.rollback(status);
             throw e;
         }
         return;
     }
 }

Spring測試代碼如下:

@ContextConfiguration(locations = {"classpath:config.xml"})
 @RunWith(SpringJUnit4ClassRunner.class)
 public class Test extends AbstractJUnit4SpringContextTests{
 ​
     @Resource
     UserService userService;
 ​
     @org.junit.Test
     public void testAdd() {
         try {
             userService.addUser(new User(null, "LiLei", 25));
         } catch (Exception e) {
         }
     }
 }

如果[2]處拋出異常,則事務執行回滾,如果[2]沒有拋出異常,則提交執行紀錄插入操作。

另一種編程式事務管理

以上這種事務管理方式容易理解,但事務管理代碼散落在業務代碼中,破壞了原有代碼的條理性,且每個事務方法中都包含了啓動事務、提交/回滾事務的功能,基於此,Spring提供了模板方法模式(TransactionTemplate)。

在config.xml配置文件中加入TransactionTemplate bean配置:

<bean id="transactionTemplate"  class="org.springframework.transaction.support.TransactionTemplate">
         <property name="transactionManager" ref="transactionManager"/>
</bean>

TransactionTemplate 的execute()方法有一個TransactionCallback類型的參數,該接口中定義了一個doInTransaction()方法,可通過匿名內部累的方式實現TransactionCallBack接口,將業務代碼寫在doInTransaction()方法中,業務代碼中不需要顯示調用任何事物管理API,除了異常回滾外,也可以在業務代碼的任意位置通過transactionStatus.setRollbackOnly();執行回滾操作。UserService服務代碼變更爲:

public class UserService {
     @Resource
     UserDAO userDAO;
     @Resource
     TransactionTemplate transactionTemplate;
     public void addUser(final User user) {
         transactionTemplate.execute(new TransactionCallback() {
             public Object doInTransaction(TransactionStatus transactionStatus) {
                 userDAO.insert(user);
                 // transactionStatus.setRollbackOnly();
                 Integer i = null;
                 if (i.equals(0)) {
 ​
                 }
                 return null;
             }
         });
     }
 }

實際場景下,我們一般使用 Spring Boot + 註解的聲明式事務。參照 《Spring Boot 事務註解詳解》

另外,也推薦看看 《Spring 事務管理 - 編程式事務、聲明式事務》 一文。

4、事務管理接口

  • PlatformTransactionManager: (平臺)事務管理器

  • TransactionDefinition: 事務定義信息(事務隔離級別、傳播行爲、超時、只讀、回滾規則)

  • TransactionStatus: 事務運行狀態

所謂事務管理,其實就是“按照給定的事務規則來執行提交或者回滾操作”。

4.1 Spring 事務如何和不同的數據持久層框架做集成

Spring並不直接管理事務,而是提供了多種事務管理器 ,他們將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。 Spring事務管理器的接口是: org.springframework.transaction.PlatformTransactionManager ,通過這個接口,Spring爲各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。

4.2 PlatformTransactionManager接口代碼如下:

PlatformTransactionManager接口中定義了三個方法:

// PlatformTransactionManager.java
 ​
 public interface PlatformTransactionManager {
 ​
     // 根據事務定義 TransactionDefinition ,獲得 TransactionStatus 。根據指定的傳播行爲,返回當前活動的事務或創建一個新事務。 
     TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
     // 根據情況,提交事務,使用事務目前的狀態提交事務
     void commit(TransactionStatus status) throws TransactionException;
     // 根據情況,回滾事務,對執行的事務進行回滾
     void rollback(TransactionStatus status) throws TransactionException; 
 }
  • PlatformTransactionManager 是負責事務管理的接口,一共有三個接口方法,分別負責事務的獲得、提交、回滾。

  • getTransaction(TransactionDefinition definition)方法,根據事務定義 TransactionDefinition ,獲得 TransactionStatus 。

    • 爲什麼不是創建事務呢?因爲如果當前如果已經有事務,則不會進行創建,一般來說會跟當前線程進行綁定。如果不存在事務,則進行創建。

    • 爲什麼返回的是 TransactionStatus 對象?在 TransactionStatus 中,不僅僅包含事務屬性,還包含事務的其它信息,例如是否只讀、是否爲新創建的事務等等。

  • commit(TransactionStatus status)方法,根據 TransactionStatus 情況,提交事務。

    • 爲什麼根據 TransactionStatus 情況,進行提交?例如說,帶@Transactional註解的的 A 方法,會調用

      @Transactional註解的的 B 方法。

      • 在 B 方法結束調用後,會執行 PlatformTransactionManager#commit(TransactionStatus status) 方法,此處事務是不能也不會提交的。

      • 而是在 A 方法結束調用後,執行 PlatformTransactionManager#commit(TransactionStatus status) 方法,提交事務。

  • rollback(TransactionStatus status)方法,根據 TransactionStatus 情況,回滾事務。

    • 爲什麼根據 TransactionStatus 情況,進行回滾?原因同 commit(TransactionStatus status) 方法。

Spring中PlatformTransactionManager根據不同持久層框架所對應的接口實現類,幾個比較常見的如下圖所示

org.springframework.transaction.support.AbstractPlatformTransactionManager ,基於 模板方法模式 ,實現事務整體邏輯的骨架,而抽象 doCommit(DefaultTransactionStatus status)doRollback(DefaultTransactionStatus status) 等等方法,交由子類類來實現。

④ 最後,不同的數據持久層框架,會有其對應的 PlatformTransactionManager 實現類,如下圖所示:

  • 所有的實現類,都基於 AbstractPlatformTransactionManager 這個骨架類。

  • HibernateTransactionManager ,和 Hibernate5 的事務管理做集成。

  • DataSourceTransactionManager ,和 JDBC 的事務管理做集成。所以,它也適用於 MyBatis、Spring JDBC 等等。

  • JpaTransactionManager ,和 JPA 的事務管理做集成。

如下,是一個比較常見的 使用JDBC或者iBatis(就是Mybatis)進行數據持久化操作的XML 方式來配置的事務管理器,使用的是 DataSourceTransactionManager 。代碼如下:

<!-- 事務管理器 -->
 <bean id="transactionManager"
 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <!-- 數據源 -->
     <property name="dataSource" ref="dataSource" />
 </bean>

4.3 TransactionDefinition接口介紹

事務管理器接口 PlatformTransactionManager 通過 getTransaction(TransactionDefinition definition) 方法來得到一個事務,這個方法裏面的參數是 TransactionDefinition類 ,這個類就定義了一些基本的事務屬性。

事務屬性

事務屬性可以理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面。

TransactionDefinition接口中的方法如下:

TransactionDefinition接口中定義了5個方法以及一些表示事務屬性的常量比如隔離級別、傳播行爲等等的常量。

下面只是列出了TransactionDefinition接口中的方法及常量隔離如下:

public interface TransactionDefinition {
     // 返回事務的傳播行爲
     int getPropagationBehavior(); 
     // 返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些數據
     int getIsolationLevel(); 
     String getName();    //返回事務的名字
     int getTimeout();      // 返回事務必須在多少秒內完成
     boolean isReadOnly();    // 返回是否優化爲只讀事務。
 /**
  * 【Spring 獨有】使用後端數據庫默認的隔離級別
  * MySQL 默認採用的 REPEATABLE_READ隔離級別
  * Oracle 默認採用的 READ_COMMITTED隔離級別
  */
     int ISOLATION_DEFAULT = -1;
 ​
 /**
  * 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀
  */
     int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
 /**
  * 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  */
     int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
 /**
  * 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
  */
     int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
 /**
  * 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。
  * 但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
  */
     int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
     // ========== 支持當前事務的情況 ========== 
 /**
  * 如果當前存在事務,則使用該事務。
  * 如果當前沒有事務,則創建一個新的事務。
  */
     int PROPAGATION_REQUIRED = 0;
 /**
  * 如果當前存在事務,則使用該事務。
  * 如果當前沒有事務,則以非事務的方式繼續運行。
  */
     int PROPAGATION_SUPPORTS = 1;
 /**
  * 如果當前存在事務,則使用該事務。
  * 如果當前沒有事務,則拋出異常。
  */
     int PROPAGATION_MANDATORY = 2;
 ​
 // ========== 不支持當前事務的情況 ========== 
 /**
  * 創建一個新的事務。
  * 如果當前存在事務,則把當前事務掛起。
  */
     int PROPAGATION_REQUIRES_NEW = 3;
 /**
  * 以非事務方式運行。
  * 如果當前存在事務,則把當前事務掛起。
  */
     int PROPAGATION_NOT_SUPPORTED = 4;
 /**
  * 以非事務方式運行。
  * 如果當前存在事務,則拋出異常。
  */
     int PROPAGATION_NEVER = 5;
 ​
 // ========== 其他情況 ========== 
 /**
  * 如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行。
  * 如果當前沒有事務,則等價於 {@link TransactionDefinition#PROPAGATION_REQUIRED}
  */
     int PROPAGATION_NESTED = 6;
 } 

(1)事務隔離級別(定義了一個事務可能受其他併發事務影響的程度):

併發事務帶來的問題

在典型的應用程序中,多個事務併發運行,經常會操作相同的數據來完成各自的任務(多個用戶對統一數據進行操作)。併發雖然是必須的,但可能會導致一下的問題。

  • 髒讀(Dirty read): 當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是“髒數據”,依據“髒數據”所做的操作可能是不正確的。

  • 丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那麼在第一個事務中修改了這個數據後,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱爲丟失修改。

    • 例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。

  • 不可重複讀(Unrepeatableread): 指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱爲不可重複讀。

  • 幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。

不可重複度和幻讀區別:

不可重複讀的重點是修改,幻讀的重點在於新增或者刪除。

例1(同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了 ):事務1中的A先生讀取自己的工資爲 1000的操作還沒完成,事務2中的B先生就修改了A的工資爲2000,導致A再讀自己的工資時工資變爲 2000;這就是不可重複讀。

例2(同樣的條件, 第1次和第2次讀出來的記錄數不一樣 ):假某工資單表中工資大於3000的有4人,事務1讀取了所有工資大於3000的人,共查到4條記錄,這時事務2 又插入了一條工資大於3000的記錄,事務1再次讀取時查到的記錄就變爲了5條,這樣就導致了幻讀。

隔離級別

TransactionDefinition 接口中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使用後端數據庫默認的隔離級別,Mysql 默認採用的 REPEATABLE_READ隔離級別,Oracle 默認採用的 READ_COMMITTED隔離級別.

  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀

  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生

  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。

  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

(2)事務傳播行爲(爲了解決業務層方法之間互相調用的事務問題):

當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。在TransactionDefinition定義中包括瞭如下幾個表示傳播行爲的常量:

支持當前事務的情況:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。

  • TransactionDefinition.PROPAGATION_MANDATORY: 如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。

不支持當前事務的情況:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 創建一個新的事務,如果當前存在事務,則把當前事務掛起。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式運行,如果當前存在事務,則把當前事務掛起。

  • TransactionDefinition.PROPAGATION_NEVER: 以非事務方式運行,如果當前存在事務,則拋出異常。 其他情況:

  • TransactionDefinition.PROPAGATION_NESTED: 如果當前存在事務,則創建一個事務作爲當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。

這裏需要指出的是,前面的六種事務傳播行爲是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 啓動的事務內嵌於外部事務中(如果存在外部事務的話),此時,內嵌事務並不是一個獨立的事務,它依賴於外部事務的存在,只有通過外部的事務提交,才能引起內部事務的提交,嵌套的子事務不能單獨提交。

類似於 JDBC 中的保存點(SavePoint)的概念,其實嵌套的子事務就是保存點的一個應用,一個事務中可以包括多個保存點,每一個嵌套子事務。另外,外部事務的回滾也會導致嵌套子事務的回滾。

(3) 事務超時屬性(一個事務允許執行的最長時間)

所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

(4) 事務只讀屬性(對事物資源是否執行只讀操作)

事務的只讀屬性是指,對事務性資源進行只讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如數據源、 JMS 資源,以及自定義的事務性資源等等。如果確定只對事務性資源進行只讀操作,那麼我們可以將事務標誌爲只讀的,以提高事務處理的性能。在 TransactionDefinition 中以 boolean 類型來表示該事務是否只讀。

(5) 回滾規則(定義事務回滾規則)

這些規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時纔會回滾,而在遇到檢查型異常時不會回滾(這一行爲與EJB的回滾行爲是一致的)。 ​ 但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常是運行期異常。

4.4 TransactionStatus接口介紹

TransactionStatus接口用來記錄事務的狀態 該接口定義了一組方法,用來獲取或判斷事務的相應狀態信息。

PlatformTransactionManager.getTransaction(…) 方法返回一個 TransactionStatus 對象。返回的TransactionStatus 對象可能代表一個新的或已經存在的事務(如果在當前調用堆棧有一個符合條件的事務)。

TransactionStatus接口接口內容如下:

public interface TransactionStatus{
     boolean isNewTransaction(); // 是否是新的事物
     boolean hasSavepoint(); // 是否有恢復點,在 {@link TransactionDefinition#PROPAGATION_NESTED} 傳播級別使用。
     void setRollbackOnly();  // 設置爲只回滾
     boolean isRollbackOnly(); // 是否爲只回滾
     boolean isCompleted; // 是否已完成
 } 
  • 爲什麼沒有事務對象呢?在 TransactionStatus 的實現類 DefaultTransactionStatus 中,有個 Object transaction 屬性,表示事務對象。

  • isNewTransaction() 方法,表示是否是新創建的事務。作用主要參照上面的 commit(TransactionStatus status) 方法的解釋。通過該方法,我們可以判斷,當前事務是否當前方法所創建的,只有創建事務的方法,才能且應該真正的提交事務

 

參照: 可能是最漂亮的Spring事務管理詳解

芋道源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章