Spring之Transaction

什麼是事務

事務是一系列操作組成的工作單元,該工作單元具有不可分割性,一損俱損。滿足ACID(原子性,一致性,隔離性,持久性)

事務按分佈式劃分可以分爲本地事務,和分佈式事務

分別由JDBC事務JTA事務與其對應。

Transaction其實在某些具體業務上,是相當實用的利器。但是我在工作之前對他的認識只是停留在概念的層面,現在想想還是很有必要好好總結一下的。

事務最經典的例子就是銀行轉賬問題,A用戶轉賬5000元給B用戶,如果在不發生任何意外的情況下,那麼是一點問題沒有的,但是如果這兩部操作中間出現了意外(例如發生了異常),很有可能這500元只轉出了,並沒有轉入。那麼這個問題的根本原因是兩個操作在代碼層面來看是相互獨立的,並不具備原子性導致的。Spring又是怎麼解決這個問題的呢?

再來通過代碼層面分析一下這個問題,轉出的時候,我們通過DataSource拿到一個Connection對象,當執行沒有異常的時候,直接提交事務,代碼並不知道還有轉入操作的存在。

所以spring針對這一點,如果在Service層的一個方法開啓了事務,那麼會關閉在這個方法中調用Dao方法自動提交事務的屬性,等到整個service執行後再做提交,具體的步驟如下:

  1. 獲取DataSource對象
  2. 通過DataSource對象獲取對應的Connection對象
  3. 關閉事務的自動提交機制,在Connection對象中
  4. 把Connection對象綁定到當前線程中
  5. 在Dao中通過取得當前線程的Connection然後執行操作
  6. 如果整個Service都ok則Commit,否則進行rollback

事務的隔離機制

數據庫的併發的問題,應運而生:例如說髒讀,虛讀,第一類丟失更新,第二類丟失更新。

解決的辦法就是通過不同的隔離機制,進行隔離:

  • Read Uncommited
  • Read Commited
  • Repeatable Read
  • Serializable

Oracle 默認使用Read Comited, Mysql默認使用 Repeatable Read。

隔離機制越高,性能越差。

事務的傳播規則

在一個事務方法中,調用了別的事務,應該按照什麼規則進行傳遞。

傳播規則一共分爲七種:

現在有這樣一種情況A方法調用了B方法。

  1. required:必須存在一個事務,如果有事務,則加入到該事務,如果沒有則新建。解讀:A如果有事務,B就用A的事務,如果A沒有事務,則B新建一個事務
  2. supports:如果有事務,則用。沒有則不用。解讀:A如果有事務,B就用A的,A如果沒有,B則不用事務。
  3. Mandatory:必須存在事務,當前如果有事務,則用。沒有則直接報異常。解讀:A如果有事務,B就用A的事務,如果沒有,則直接報錯。
  4. required_new: 不管當前是否存在事務,都會創建一個新的,這個在平常比較多。
  5. not_supports: 以非事務方式執行,如果當前存在事務,則將當前事務掛起 解讀:A有自己的事務,B不使用A的事務,B不參與A事務的管理。
  6. never:不支持事務,當前如果存在事務,則拋出異常。
  7. nested:寄生事務。如果內部事務進行回滾,不會影響到外部事務,如果外部事務回滾了,內部事務會被影響。

Spring對事務的支持

Spring的事務管理一定要在業務層上的

  • PlatformTransactionManager 根據TransactionBefination提供的事務信息,進行配置。是多種事務管理器的基類。Hibernate使用的是HibernateTransactionManager,Mybatis/JDBC使用的是DataSourceTransactionManager。PlatformTransactionManager 一共擁有三個方法:
    • getTransaction(TransactionDefination),在當前環境中取得一個事務,如果不存在,則新建。有點像是一種緩存機制
    • commit:提交事務
    • rollback:回滾事務
  • TransactionDefination:封裝了事務隔離級別,超時時間等。
  • TransactionStatus:封裝了事務具體運行的狀態,是否是新開的事務,是否已經提交事務
Xml方式進行配置:

下方是Spring官網給的例子

//業務接口:
public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
<!--xml文件關於事務的配置-->
<?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"/>

   <!-- 配置數據庫連接池,因爲連接池會作爲屬性注入到TransactionManager中 -->
    <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>

    <!-- 配置PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
  
  <!-- 配置transaction 具體的一些配置 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- 如果是方法名以get作爲開頭的,說明是查詢方法,那麼配置只讀操作-->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他的增和改操作,就是用默認的即可-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
  
    <!-- 使用Aop把transactionManager作爲對業務邏輯的增強操作 -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config> 

    <!-- other <bean/> definitions here -->

</beans>

<tx:advice/>中的一些詳細配置,官網也給出了相應的一些說明,如下圖:

Attribute Required? Default Description
name Yes 事務管理的方法名稱,並且支持通配符,例如 get*, handle*, on*Event, 等等).
propagation No REQUIRED Transaction propagation behavior.
isolation No DEFAULT 事務的隔離級別,當傳遞規則爲 REQUIRED or REQUIRES_NEW纔可以設置,當是默認值default的時候,指的是使用數據庫隔離級別。其他四種都是Spring 通過代碼模擬出來的
timeout No -1 事務超時時間 (seconds),當傳遞規則爲 REQUIRED or REQUIRES_NEW纔可以設置,默認值-1代表使用數據庫本身的值,一般情況下,不需要進行修改。
read-only No false 一般對查詢進行設置只讀,可以提升事務的效率。只應用於 REQUIRED or REQUIRES_NEW.
rollback-for No java.lang.RunTimeException 遇到什麼異常需要做事務的回滾,例如,com.foo.MyBusinessException,ServletException.
no-rollback-for No 遇到什麼異常不做回滾,com.foo.MyBusinessException,ServletException.
Java註解方式

首先我們需要在配置類上,開啓對事務的支持,使用@EnableTransactionManagement

官網的例子:

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional 註解可以用來放在實現類上,也可以放在接口上,最好是放在實現類上。如果加在了實現類上,那麼也就是說對這個類裏的所有方法都支持開啓事務。如果有哪個類需要一些定製化的屬性,只需要在方法上再加上這個註解並且貼上定製的屬性即可。

@Transactional可以使用的屬性:

Property Type Description
value String
propagation enum: Propagation
isolation enum: Isolation 隔離級別的設置,用於傳遞屬性爲 REQUIRED or REQUIRES_NEW.
timeout int (in seconds of granularity) 事務超時時間用於傳遞屬性爲REQUIRES_NEW.
readOnly boolean 是否爲只讀. 用於傳遞屬性爲 REQUIRES_NEW.
rollbackFor Array of Class objects, which must be derived from Throwable.
rollbackForClassName Array of class names. The classes must be derived from Throwable. 哪些異常類處罰會導致回滾(使用異常類名)
noRollbackFor Array of Class objects, which must be derived from Throwable. 哪些異常類處罰不會導致回滾(使用異常類)
noRollbackForClassName Array of String class names, which must be derived from Throwable. 哪些異常類處罰不會導致回滾(使用異常類名)

可以看出來這些屬性與xml配置的大同小異。

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