Spring之Transaction

什么是事務(wù)

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

事務(wù)按分布式劃分可以分為本地事務(wù),和分布式事務(wù)

分別由JDBC事務(wù)JTA事務(wù)與其對(duì)應(yīng)。

Transaction其實(shí)在某些具體業(yè)務(wù)上,是相當(dāng)實(shí)用的利器。但是我在工作之前對(duì)他的認(rèn)識(shí)只是停留在概念的層面,現(xiàn)在想想還是很有必要好好總結(jié)一下的。

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

再來通過代碼層面分析一下這個(gè)問題,轉(zhuǎn)出的時(shí)候,我們通過DataSource拿到一個(gè)Connection對(duì)象,當(dāng)執(zhí)行沒有異常的時(shí)候,直接提交事務(wù),代碼并不知道還有轉(zhuǎn)入操作的存在。

所以spring針對(duì)這一點(diǎn),如果在Service層的一個(gè)方法開啟了事務(wù),那么會(huì)關(guān)閉在這個(gè)方法中調(diào)用Dao方法自動(dòng)提交事務(wù)的屬性,等到整個(gè)service執(zhí)行后再做提交,具體的步驟如下:

  1. 獲取DataSource對(duì)象
  2. 通過DataSource對(duì)象獲取對(duì)應(yīng)的Connection對(duì)象
  3. 關(guān)閉事務(wù)的自動(dòng)提交機(jī)制,在Connection對(duì)象中
  4. 把Connection對(duì)象綁定到當(dāng)前線程中
  5. 在Dao中通過取得當(dāng)前線程的Connection然后執(zhí)行操作
  6. 如果整個(gè)Service都o(jì)k則Commit,否則進(jìn)行rollback

事務(wù)的隔離機(jī)制

數(shù)據(jù)庫的并發(fā)的問題,應(yīng)運(yùn)而生:例如說臟讀,虛讀,第一類丟失更新,第二類丟失更新。

解決的辦法就是通過不同的隔離機(jī)制,進(jìn)行隔離:

  • Read Uncommited
  • Read Commited
  • Repeatable Read
  • Serializable

Oracle 默認(rèn)使用Read Comited, Mysql默認(rèn)使用 Repeatable Read。

隔離機(jī)制越高,性能越差。

事務(wù)的傳播規(guī)則

在一個(gè)事務(wù)方法中,調(diào)用了別的事務(wù),應(yīng)該按照什么規(guī)則進(jìn)行傳遞。

傳播規(guī)則一共分為七種:

現(xiàn)在有這樣一種情況A方法調(diào)用了B方法。

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

Spring對(duì)事務(wù)的支持

Spring的事務(wù)管理一定要在業(yè)務(wù)層上的

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

下方是Spring官網(wǎng)給的例子

//業(yè)務(wù)接口:
public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
<!--xml文件關(guān)于事務(wù)的配置-->
<?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">

    <!-- 首先將剛剛的業(yè)務(wù)類注入進(jìn)容器中 -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

   <!-- 配置數(shù)據(jù)庫連接池,因?yàn)檫B接池會(huì)作為屬性注入到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"/>
            <!-- 其他的增和改操作,就是用默認(rèn)的即可-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
  
    <!-- 使用Aop把transactionManager作為對(duì)業(yè)務(wù)邏輯的增強(qiáng)操作 -->
    <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/>中的一些詳細(xì)配置,官網(wǎng)也給出了相應(yīng)的一些說明,如下圖:

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

首先我們需要在配置類上,開啟對(duì)事務(wù)的支持,使用@EnableTransactionManagement

官網(wǎng)的例子:

@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 注解可以用來放在實(shí)現(xiàn)類上,也可以放在接口上,最好是放在實(shí)現(xiàn)類上。如果加在了實(shí)現(xiàn)類上,那么也就是說對(duì)這個(gè)類里的所有方法都支持開啟事務(wù)。如果有哪個(gè)類需要一些定制化的屬性,只需要在方法上再加上這個(gè)注解并且貼上定制的屬性即可。

@Transactional可以使用的屬性:

Property Type Description
value String
propagation enum: Propagation
isolation enum: Isolation 隔離級(jí)別的設(shè)置,用于傳遞屬性為 REQUIRED or REQUIRES_NEW.
timeout int (in seconds of granularity) 事務(wù)超時(shí)時(shí)間用于傳遞屬性為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. 哪些異常類處罰會(huì)導(dǎo)致回滾(使用異常類名)
noRollbackFor Array of Class objects, which must be derived from Throwable. 哪些異常類處罰不會(huì)導(dǎo)致回滾(使用異常類)
noRollbackForClassName Array of String class names, which must be derived from Throwable. 哪些異常類處罰不會(huì)導(dǎo)致回滾(使用異常類名)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容