Spring事務管理

Spring數據庫事務管理器的設計java

在Spring中數據庫事務是經過PlatformTransactionManager進行管理的。附TransactionTemplate重要源碼。spring

@Override  
@Nullable  
public <T> T execute(TransactionCallback<T> action) throws TransactionException {  
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");  
        //使用自定義的事務管理器          
    if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {  
        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);  
    }else {//系統默認管理器  
               //獲取事務狀態  
        TransactionStatus status = this.transactionManager.getTransaction(this);  
        T result;  
        try {  
            result = action.doInTransaction(status);//回調接口方法  
        }catch (RuntimeException | Error ex) {  
            // 回滾異常方法  
            rollbackOnException(status, ex);  
            throw ex;  
        }catch (Throwable ex) {  
            // 回滾異常方法  
            rollbackOnException(status, ex);  
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");  
        }  
        this.transactionManager.commit(status);//提交事務  
        return result;//返回結果  
    }  
}  

能夠清楚的看到:數據庫

  • 事務的建立、提交和回滾是經過PlatformTransactionManager接口來完成的
  • 當事務產生異常時會回滾事務,在默認實現中全部的異常都會回滾。咱們能夠經過配置去修改在某些異常發生時回滾或者不回滾事務
  • 當無異常時,提交事務

配置事務管理器express

目前咱們討論JDBC和MyBatis,使用最多的事務管理器是DataSourceTransactionManager,其它持久層框架後續介紹。編程

<?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:context="http://www.springframework.org/schema/context"  
    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/context  
        https://www.springframework.org/schema/context/spring-context.xsd  
        http://www.springframework.org/schema/aop  
        https://www.springframework.org/schema/aop/spring-aop.xsd  
        http://www.springframework.org/schema/tx  
        https://www.springframework.org/schema/tx/spring-tx.xsd">  
          
        <context:component-scan base-package="com.wise.tiger"/>  
      
        <context:property-placeholder location="classpath:dbcp-config.properties"/>  
        <!-- 配置數據源 -->  
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" >  
            <property name="driverClassName" value="${driverClassName}" />  
            <property name="url" value="${url}" />  
            <property name="username" value="${jdbc.username}" />  
            <property name="password" value="${password}" />  
            <property name="defaultAutoCommit" value="${defaultAutoCommit}"/>  
            <property name="connectionProperties" value="${connectionProperties}"/>  
        </bean>  
              
        <!--配置數據源事務管理器 -->  
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
            <property name="dataSource" ref="dataSource"/>  
        </bean>  
</beans>  

在spring中可使用聲明式事務和編程式事務,現在編程式事務幾乎不用了,由於它會產生冗餘,代碼可讀性較差。聲明式事務又能夠分爲xml配置和註解事務,但xml方式已經不經常使用了,因此這裏只簡單交代下它的用法,目前主流方法是註解@Transactional。安全

聲明式事務網絡

聲明式事務是一種約定性的事務,在大部分的狀況下,使用數據庫事務時,大部分場景是在代碼中發生了異常時,須要回滾事務,而不發生異常時則是提交事務,從而保證數據庫數據的一致性。從這點出發,Spring給了一個約定(相似於AOP開發給的約定),你的業務方法不發生異常,事務管理器就提交事務,發生異常則讓事務管理器回滾事務。併發

首先聲明式事務容許自定義事務接口——TransactionDefinition,它能夠由xml或者註解@Transactional進行配置,到了這裏咱們先談談@Transactional的配置項app

@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
@Documented  
public @interface Transactional {  
  
    @AliasFor("transactionManager")  
    String value() default "";  
  
    @AliasFor("value")  
    String transactionManager() default "";  
  
    Propagation propagation() default Propagation.REQUIRED;  
  
    Isolation isolation() default Isolation.DEFAULT;  
  
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;  
  
    boolean readOnly() default false;  
  
    Class<? extends Throwable>[] rollbackFor() default {};  
  
    String[] rollbackForClassName() default {};  
      
    Class<? extends Throwable>[] noRollbackFor() default {};  
  
    String[] noRollbackForClassName() default {};  
}  

Transactional的配置項框架

 

配置項 含義 備註
value 定義事務管理器

spring容器中的一個Bean id,

這個Bean須要實現接口PlatformTransactionManager

transactionManager 同上 同上
isolation 隔離級別 數據庫在併發事務時的概念,默認取值爲數據庫的隔離級別
propagation 傳播行爲 方法之間調用問題,默認取值爲Propagation.REQUIRED
timeout 超時時間 單位爲秒,當超時時,會引起異常,默認會致使事務回滾
readOnly 是否開啓只讀事務 默認值:false
rollbackFor 回滾事務的異常類定義 只有當方法產生所定義的異常時,纔會回滾事務
rollbackForClassName 回滾事務的異常類名定義 同rollbackFor,只是使用類名稱定義
noRollbackFor 當產生哪些異常不回滾事務 當產生所定義異常時,Spring將繼續提交事務
noRollbackForClassName 同noRollbackFor 同noRollbackFor,只是使用類名稱定義

只須要在xml配置文件中加入以下配置就可使用@EnableTransactionManagement@Transactional配置事務了

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

** 使用xml進行配置事務管理器**

<!-- 配置事務通知 -->  
<tx:advice id="txAdvice" transaction-manager="txManager">  
    <tx:attributes>  
        <!-- 根據方法名指定事務的屬性  -->  
        <tx:method name="bookService" propagation="REQUIRED"/>  
        <tx:method name="get*" read-only="true"/>  
        <tx:method name="find*" read-only="true"/>  
        <tx:method name="*"/>  
    </tx:attributes>  
</tx:advice>  
<!-- 配置事務切入點,以及把事務切入點和事務屬性關聯起來 -->  
<aop:config>  
    <aop:pointcut expression="execution(* com.wise.tiger.service.*.*(..))"  
        id="txPointcut"/>  
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>  
</aop:config>  

選擇隔離級別

在互聯網應用中,不但要考慮數據庫數據的一致性,並且還要考慮系統的性能,通常而言,從髒讀到序列化,系統性能直線降低。所以設置高的級別,好比序列化,會嚴重壓制併發,從而引起大量的線程掛起,直到得到鎖才能進一步操做,而恢復時又須要大量的等待時間。所以在通常的在購物類應用中,經過隔離級別來控制事務一致性的方式又被排除了,而對於髒讀又風險過大,在大部分場景下,企業會選擇讀/寫提交的方式設置事務,這樣既有助於提升併發,又壓制了髒讀,可是對於數據一致性問題並無解決,後面討論解決。對於通常的應用均可以使用@Transactional方法進行配置。

隔離級別須要根據併發的大小和性能來作出決定,對於併發不大又要保證數據安全性的可使用序列化的隔離級別,這樣就能保證數據庫在多事務環境中的一致性。

@Transactional(isolation = Isolation.SERIALIZABLE)  
public void save(Book book) {  
    bookDao.insertBook(book);  
}  

只是這樣的代碼會使得數據庫的併發能力低下,在搶購商品的場景下出現卡頓的狀況,因此在高併發的場景下這樣的代碼並不適用。

註解@Transactional的默認隔離級別是Isolation.DEFAULT,其含義是默認的,隨數據庫的默認值而變化。由於不一樣的數據庫支持的隔離級別是不同的,MySQL支持所有四種隔離級別,默認爲可重複讀的隔離級別。而Oracle只支持讀/寫提交和序列化,默認讀/寫提交.

傳播行爲

傳播行爲是指方法之間的調用事務策略問題。在大部分狀況下,咱們都但願事務可以同時成功或者同時失敗。但也會有例外,假如如今須要實現信用卡的還款功能,有一個總的代碼調用邏輯————RepaymentBatchService的batch方法,那麼它要實現的是記錄還款成功的總卡數和對應完成的信息,而每一張卡的還款則是經過RepaymentService的repay方法完成的。

首先來分析業務。若是隻有一條事務,那麼當調用RepaymentService的repay方法對某一張信用卡進行還款時,不幸的事發生了,它發生了異常。若是將這條事務回滾,就會形成全部的數據操做都會回滾,那些已經正常還款的用戶也會還款失敗,這將是一個糟糕的結果。當batch方法調用repay方法時,它會爲repay方法建立一個新的事務。當這個方法產生異常時,只會回滾它自身的事務,而不會影響主事務和其它事務,這樣就避免了上面的問題

Spring中傳播行爲的類型是經過一個枚舉去定義的org.springframework.transaction. annotation.Propagation

傳播行爲 含義 備註
REQUIRED

當方法調用時,若是不存在當前事務,那麼就建立事務;若是以前的方法已經存在事務了,那麼就沿用以前的事務

spring默認
SUPPORTS 支持當前事務,若是當前沒有事務,就以非事務方式執行。  
MANDATORY 方法必須在事務內運行

使用當前的事務,

若是當前沒有事務,就拋出異常。

REQUIRES_NEW 新建事務,若是當前存在事務,把當前事務掛起 事務管理器會打開新的事務運行該方法
NOT_SUPPORTED 以非事務方式執行操做,若是當前存在事務,就把當前事務掛起,直到該方法結束才恢復當前事務 適用於那些不須要事務的SQL
NEVER 以非事務方式執行,若是當前存在事務,則拋出異常。  
NESTED 嵌套事務,也就是調用方法若是拋出異常只回滾本身內部執行的SQL,而不回滾主方法的SQL 它的實現存在兩種狀況:1.若是當前數據庫支持保存點(savepoint),那麼它就會在當前事務上使用保存點技術;若是發生異常,則將方法內執行的SQL回滾到保存點上,而不是所有回滾;不然就等同於REQUIRES_NEW建立新的事務運行方法代碼

通常而言,企業級應用中主要關注的是REQUEIRES_NEW和NESTED

@Transactional的自調用失效問題

       註解@Transactional底層的實現是SpringAOP技術,而SpringAOP技術使用的是動態代理。這就意味着對於靜態(static)方法和非public方法,註解@Transactional是失效的。還有一個更隱祕的,並且在使用過程當中極其容易犯錯的——自調用。所謂自我調用,就是一個類的方法調用自身另一個方法的過程

@Service  
public class BookServiceImpl implements BookService {  
    @Autowired  
    private BookMapper bookMapper;  
  
    // 傳播行爲定義爲REQUIRED  
    @Override  
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED,timeout = 1)  
    public int insertBookList(List<Book> bookList) {  
        int count=0;  
                bookList.forEach(book-> count += this.insertBook(book));//調用本身的方法,產生自調用問題  
        return count;  
    }  
   
    // 傳播行爲定義爲REQUIRES_NEW,每次調用產生新事務  
    @Override  
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW,timeout = 1)  
    public int insertBook(Book book) {  
        return bookMapper.insertBook(book);  
    }  
}  

經過測試,在insertBook上標註的@Transactional失效了,這是一個很容易掉進去的陷阱。

出現這個問題的根本緣由在於AOP的實現原理。@Transactional實現原理是AOP,而AOP的實現原理是動態代理,類中的方法調用本身的另外一個方法,並不存在代理對象的調用,這樣就不會產生AOP去爲咱們設置@Transactional配置的參數,這樣就出現了自調用註解失效的問題。爲了解決這個問題,一方面咱們可使用兩個服務類,另一方面能夠直接從容器中獲取service的代理對象。

典型錯誤用法剖析

MVC模型中Controller中調用Service的問題

  • 當一個Controller使用Service方法時,若是這個Service標註有@Transactional,那麼它就會啓用一個事務,而一個Service方法完成後,它就會釋放該事務
  • 當一個Controller聲明的方法中,調用了兩次Service方法,這兩個方法是有各自單獨的事務的。若是屢次調用,且不在同一個事務中,這會形成不一樣時提交和回滾不一致的問題

防止過長時間佔用事務

  • 大型互聯網系統中,一個數據庫的連接可能也就是50條左右。
  • @Transactional的Service類中的方法代碼,做爲一個事務總體,與數據庫沒有交互的代碼,如網絡請求,文件上傳也會佔用事務時間,由於只有方法運行完成後,返回Result後纔會關係數據庫資源

錯誤異常捕獲語句

  • MVC模型中服務類分爲原子服務類和組合服務類
  • ...方法已經存在異常了,因爲開發者不瞭解Spring的事務約定,在兩個操做方法里加入本身的try...catch..語句,就可能形成數據庫操做發生異常的時候,被代碼中的try...catch..所捕獲,Spring在事務約定的流程中再也得不到任何異常信息,此時Spring就會提交事務,形成數據不一致
  • 解決方法時將捕獲的異常向上拋出
相關文章
相關標籤/搜索