Spring事務

1. 數(shù)據(jù)庫事務特征

1.1 ACID特性

事務 (Transaction) 是數(shù)據(jù)庫系統(tǒng)中一系列操作的一個邏輯單元煮岁,所有操作要么全部成功要么全部失敗幅狮。

事務是區(qū)分文件存儲系統(tǒng)Nosql 數(shù)據(jù)庫重要特性之一茂蚓,其存在的意義是為了保證即使在并發(fā)情況下也能掙錢的執(zhí)行crud操作蠢护。怎樣才算是正確的呢幻碱?這時提出了事務需要保證的四個特性即ACID

  • A:原子性(atomicity)
    一個事務 (transaction) 中的所有操作,要么全部完成灌侣,要么全部不完成,不會結束在中間某個環(huán)節(jié)裂问。事務在執(zhí)行過程中發(fā)生錯誤侧啼,會被回滾 (Rollback) 到事務開始前的狀態(tài),就像這個事務從來沒有執(zhí)行過一樣堪簿。

    原子性表現(xiàn)為操作不能被分割痊乾。

  • C:一致性(consistency)
    在事務開始之前和事務結束以后,數(shù)據(jù)庫的完整性沒有被破壞椭更。數(shù)據(jù)庫要一直處于一致的狀態(tài)哪审,事務開始前是一個一致狀態(tài),事務結束后是另一個一致狀態(tài)虑瀑,事務將數(shù)據(jù)庫從一個一致狀態(tài)轉移到另一個一致狀態(tài)湿滓。

  • I:隔離性(isolation)
    數(shù)據(jù)庫允許多個并發(fā)事務同時對其數(shù)據(jù)進行讀寫和修改的能力畏腕,隔離性可以防止多個事務并發(fā)執(zhí)行時由于交叉執(zhí)行而導致數(shù)據(jù)的不一致。事務隔離分為不同級別茉稠,包括讀未提交 (read uncommitted)描馅、讀已提交(read committed)、可重復讀 (repeatable read) 和串行化 (Serializable)而线。

  • D:持久性(durability)
    事務外理結束后铭污,對數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會丟失膀篮。

擴展

WAL原則

InnoDB 的 ARIES 三原則 Write Ahead Logging (WAL)

  • 日志成功寫入后事務就不會丟失嘹狞,后續(xù)由checkpoint機制來保證磁盤物理文件與redo log達到一致性;
  • 利用redo log來記錄變更后的數(shù)據(jù)誓竿,即redo里記錄事務數(shù)據(jù)變更后的值磅网;
  • 利用undo log來記錄變更前的數(shù)據(jù),即undo里記錄事務數(shù)據(jù)變更前的值筷屡,用于回滾和其他事務多版本讀涧偷。

并發(fā)事務控制

  • 單版本控制-鎖
    鎖用獨占的方式來保證在只有一個版本的情況下事務之間相互隔離。

    在MySQL事務中毙死,鎖的實現(xiàn)與隔離級別有關系,在RR (Repeatable Read) 隔離級別下扼倘,MySQL為了解

    決幻讀的問題确封,以犧牲并行度為代價,通過 Gap 鎖來防止數(shù)據(jù)的寫入再菊,而這種鎖爪喘,因為其并行度不夠,

    沖突很多纠拔,經(jīng)常會引起死鎖”#現(xiàn)在流行的 Row 模式可以避免很多沖突甚至死鎖問題,所以推薦默認使用Row+

    RC (Read Committed) 模式的隔離級別绿语,可以很大程度上提高數(shù)據(jù)庫的讀寫并行度秃症。

  • 多版本控制 - MVCC

    在數(shù)據(jù)庫中,為了實現(xiàn)高并發(fā)的數(shù)據(jù)訪問吕粹,對數(shù)據(jù)進待多版本處理种柑,并通過事務的可見性來保證事務能看到自己應該看到的數(shù)據(jù)版本。MVCC最大的好處是讀不加鎖匹耕,讀寫不沖突聚请。

    每一次對數(shù)據(jù)庫的修改,都會在 Undo 日志中記錄當前修改記錄的事務號及修改前數(shù)據(jù)狀態(tài)的存儲地址(即ROLL_PTR),以便在必要的時候可以回滾到老的數(shù)據(jù)版本驶赏。例如炸卑,一個讀事務查詢到當前記錄,而最新的事務還未提交煤傍,根據(jù)原子性盖文,讀事務看不到最新數(shù)據(jù)。

1.2 事務隔離級別

在高并發(fā)的情況下蚯姆,要完全保證其ACID特性是非常困難的五续,除非把所有的事務串行化執(zhí)行,但帶來的負面的影響將是性能大打折扣龄恋。很多時候我們有些業(yè)務對事務的要求是不一樣的疙驾,所以數(shù)據(jù)庫中設計了四種隔離級別,供用戶基于業(yè)務進行選擇郭毕。

數(shù)據(jù)庫默認隔離級別:
  • Oracle中默認級別:Read committed
  • MySQL中默認級別:Repeatable read(在數(shù)據(jù)庫內(nèi)均表現(xiàn)為大寫)
# 查看 mysql 的默認隔離級別
SELECT @@tx_isolation

# 設置為讀未提交
SET tx_isolation='read-uncommitted';

# 設置為讀已提交
SET tx_isolation='read-committed';

# 設置為可重復讀
SET tx_isolation='REPEATABLE-READ';

# 設置為串行化
SET tx_isolation='SERIALIZABLE'
隔離級別 臟讀(Dirty Read) 不可重復讀(NonRepeatable Read) 幻讀(Phantom Read)
未提交讀(Read uncommitted) 可能 可能 可能
已提交讀(Read committed) 不可能 可能 可能
可重復讀(Repeatable read) 不可能 不可能 可能
可串行化(Serializable) 不可能 不可能 不可能
臟讀
   一個事務讀取到另一個事務未提交的更新數(shù)據(jù)

session_1

# 設置為讀未提交
SET tx_isolation='read-uncommitted';

BEGIN;

INSERT INTO `tbl_employee` (`last_name`,`email`,  `gender`)
    VALUES ( '2222', '111111@qq.com', '1' );
# 1. 執(zhí)行到此處它碎,執(zhí)行session_2

ROLLBACK;
# 3. 執(zhí)行到此處,再執(zhí)行session_2

COMMIT;  -- 無用

session_2

# 設置為讀未提交
SET tx_isolation='read-uncommitted';

SELECT * FROM `tbl_employee`;
# 2. 執(zhí)行完成显押,session_1 事務未提交扳肛,卻讀出了數(shù)據(jù);回到session_1執(zhí)行回滾
# 4. 此時查詢不到數(shù)據(jù)煮落,說明第2步出現(xiàn)臟讀
不可重復讀
   在同一事務中敞峭,多次讀取同一數(shù)據(jù)返回的結果有所不同,換句話說蝉仇,后續(xù)讀取可以讀到另一事務已提交的更新數(shù)據(jù)。相反殖蚕,“可重復讀”在同一事務中多次讀取數(shù)據(jù)時轿衔,能夠保證所讀數(shù)據(jù)一樣,也就是后續(xù)讀取不能讀到另一事務已提交的更新數(shù)據(jù)睦疫。

事務B修改數(shù)據(jù)導致當前事務A前后讀取數(shù)據(jù)不一致害驹,側重點在于事務B的修改

當前事務讀到了其他事務修改的數(shù)據(jù)

session_1

# 設置為讀已提交
SET tx_isolation='read-committed';

BEGIN;

SELECT * FROM `tbl_employee`;
# 1. 執(zhí)行session_2中內(nèi)容

SELECT * FROM `tbl_employee`;

# 3. 和前面查詢的數(shù)據(jù)不一致
COMMIT;

session_2

# 設置為讀已提交
SET tx_isolation='read-committed';

UPDATE `tbl_employee` SET email = "222222@qq.com" WHERE id = 5;
# 2. 執(zhí)行session_1中內(nèi)容
幻讀
    查詢表中一條數(shù)據(jù),如果不存在就插入一條蛤育,并發(fā)的時候卻發(fā)現(xiàn)宛官,里面居然有兩條相同的數(shù)據(jù)。

    事務A修改表中數(shù)據(jù)瓦糕,此時事務B插入一條新數(shù)據(jù)底洗,事務A查詢發(fā)現(xiàn)表中還有沒修改的數(shù)據(jù),像是出現(xiàn)幻覺

    事務A讀到了事務B新增的數(shù)據(jù)咕娄,導致結果不一致亥揖,側重點在于事務B新增數(shù)據(jù)

session_1

# 設置為可重復讀
SET tx_isolation='REPEATABLE-READ';

BEGIN;

SELECT * FROM `tbl_employee` WHERE last_name = "jack";

# 1. 此時在session_2事務插入數(shù)據(jù)

SELECT * FROM `tbl_employee` WHERE last_name = "jack";

INSERT INTO `tbl_employee` (`last_name`,`email`,  `gender`)
    VALUES ( 'jack', '333333333@qq.com', '1' );
    
SELECT * FROM `tbl_employee` WHERE last_name = "jack";  

UPDATE `tbl_employee` SET email = "444444444@qq.com" WHERE last_name = "jack";

SELECT * FROM `tbl_employee` WHERE last_name = "jack";

COMMIT;

session_2

# 設置為可重復讀
SET tx_isolation='REPEATABLE-READ';

INSERT INTO `tbl_employee` (`last_name`,`email`,  `gender`)
    VALUES ( 'jack', '1111111@qq.com', '1' );

2. Spring事務應用及源碼分析

2.1 Spring事務相關API

Spring 事務是在數(shù)據(jù)庫事務的基礎上進行封裝擴展,其主要特性如下:

  • 支持原有的數(shù)據(jù)庫事務的隔離級別,加入了事務傳播的概念
  • 提供多個事務的合并或隔離的功能
  • 提供聲明式事務费变,讓業(yè)務代碼與事務分離摧扇,事務變得更易用(AOP)

Spring 提供了事務相關接口:

  • TransactionDefinition

    事務定義:事務的隔離級別和事務的傳播行為

  • TransactionAttribute

    事務屬性,實現(xiàn)了對回滾規(guī)則的擴展(外理異常)

  • PlatformTransactionManager

    事務管理器

  • TransactionStatus

    事務運行時狀態(tài)

相關實現(xiàn)類

  • TransactionIntercenter

    事務攔截器挚歧,實現(xiàn)了 MethodInterceptor

  • TransactionAspectSupport

    事務切面支持扛稽,內(nèi)部類Transactionlnfo封裝了事務相關屬性
    TransactionAspectSupport.Transactionlnfo

2.2 編程式事務

public class SpringTransactionExample {
    private static String url = "jdbc:mysql://127.0.0.1:3306/test";
    private static String user = "root";
    private static String password = "root";
    
    public static void main(string[] args) {
      // 獲取數(shù)據(jù)源
      final Datasource ds = new DriverManagerDatasource(url, user, password);
      //編程式事務
      final TransactionTemplate template = new TransactionTemplate();
      // 設置事務管理器
      template.setTransactionManager(new DataSourceTransactionManager(ds));
        
      template.execute(new TransactionCa11back<0bject>() {
         @override
         public object doInTransaction(Transactionstatus status) {
             Connection conn = DatasourceUtils.getConnection(ds);
             object savePoint = null;
             try {
                 {
                    // 插入
                    PreparedStatement prepare = conn.preparestatement("insert INTO account (accountName,user,money) VALUES (?, ?, ?)");
                    preparp.setstring(1, "111");
                    prepare.setstring(2, "aaa");
                    prepare.setInt(3, 10000);
                    prepare.executeUpdate():;
                 }
                 //設置保存點
                savepoint = status.createSavepoint();
                {
                   //捕入
                   PreparedStatement prepare = conn.preparestatement("insert INTO account (accountName.user,money) VALUES (?, ?, ?)");
                    prepare.setstring(1,"222");
                    prepare.setstring(2, "bbb");
                    prepare.setInt(3, 10000);
                    prepare.executeupdate() ; 
                }
                {
                    //更新
                    Preparedstatement prepare = conn.preparestatement("UPDATE account SET money= money+100 where user=?");
                    prepare.setstring(1, "aaa");
                    prepare.executeUpdate();
                    //int i=1/0;
                }
             } catch (SQLException e) {
                e.printstackTrace();
             } catch (Exception e) {
                System.out.print1n("更新失敗");
                if (savePoint != null) [
                    status.ro11backTosavepoint(savePoint);
                } else {
                    status.setRollbackOnly();
                }  
             }
             return null;
       }
    });
}

2.3 聲明式事務

Xml配置

添加tx名字空間

xmlns:tx="http://www.springframework.org/schema/tx"

配置事務管理器和數(shù)據(jù)源

<bean id="datasource"
  class="org.springframework.jdbc.datasource.DriverManagerDatasource">
    <constructor-arg name="ur1" value="jdbc:mysq1://127 .0.0.1/test"/>
    <constructor-arg name="username" va]ue="root"/>
    <constructor-arg name="password" value="root"/>
</bean>
<bean id="txManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="datasource" ref="datasource"/>
</bean>

事務切面配置

<!-- 事物切面配置 -->
<tx:advice id="advice" transaction-manager="txManager"
    <tx:attributes>
        <tx:method name="update*" propagation-"REQUIRED" read-only="false" rollback-for="Java.1ang.Exception"/>
        <tx:method name="add*" propagation="REQUIRED" read-only="false"/>
    </tx:attributess>
</tx:advice>
<!-- 事物切入點 -->
<aop:config>
    <aop:pointcut expression="execution(* bat.ke.qq.com.service.*.*(..))" id="txPointCut"/>
    <aop:advisor advice-ref="advice" pointcut-ref="txPointCut"/>
</aop:config>

@Transactional

<!-- 開啟事務控制的注解支持 -->
<tx:annotation-driven transaction-manager="txManager"/>

事務主機配置,作用于類滑负,方法上

屬性名 說明
name 當在配置文件中有多個TransactionManager在张,可以用該屬性指定選擇哪個事務管理器
propagation 事務的傳播行為,默認值為REOUIRED
isolation 事務的隔離度橙困,默認值采用DEFAULT
timeout 事務的超時時間瞧掺,默認值為-1。如果超過該時間限制但事務還沒有完成凡傅,則自動回滾事務
read-only 指定事務是否為只讀事務辟狈,默認值為false;為了忽略那些不需要事務的方法夏跷,比如讀取數(shù)
據(jù)哼转,可以設置 read-only 為 true。
rollback-for 用于指定能夠觸發(fā)事務回滾的異常類型槽华,如果有多個異常類型需要指定壹蔓,各類型之間可以通
過逗號分隔
no-rollback-for 拋出 no-rollback-for 指定的異常類型,不回滾事務

Java Configuration

@EnableTransactionManagement

利用 TransactionManagementConfigurationSelector 向容器中注冊兩個組件

  • AutoProxyRegistrar

    給容器中注冊一個 InfrastructureAdvisorAutoProxyCreator 的后置處理器猫态,返回一個代理對象 (增強器)佣蓉,代理對象執(zhí)行方法利用攔截器鏈進行調用

  • ProxyTransactionManagementConfiguration 是一個 @Configuration

    • 給容器中注冊事務增強器 transactionAcvisor
    • AnnotationTransactionAttributesource 解析事務注解
    • 事務欄截器 transactionInterceptor

Spring的事務傳播機制

    多個事務方法相互調用時,事務如何在這些方法之間進行傳播亲雪,Spring中提供了 `7種` 不同的傳播特性勇凭,來保證事務的正常執(zhí)行
  • REQUIRED:默認的傳播特性,如果當前沒有事務义辕,則新建一個事務虾标,如果當前存在事務,則加入這個事務
  • SUPPORTS:當前存在事務灌砖,則加入當前事務璧函,如果當前沒有事務,則以非事務的方式執(zhí)行
  • MANDATORY:當前存在事務基显,則加入當前事務蘸吓,如果當前事務不存在,則拋出異常
  • REQUIRED_NEW :創(chuàng)建一個新事務续镇,如果存在當前事務美澳,則掛起該事務
  • NOT_SUPPORTED:以非事務方式執(zhí)行,如果存在當前事務,則掛起當前事務
  • NEVER:不使用事務制跟,如果當前事務存在舅桩,則拋出異常
  • NESTED:如果當前事務存在,則在嵌套事務中執(zhí)行雨膨,否則REQUIRED的操作一樣

NESTED 和 REQUIRED_NEW 的區(qū)別:

REQUIRED_NEW 是新建一個事務并且新開始的這個事務與原有事務無關擂涛,而 NESTED 則是當前存在事務時會開啟一個嵌套事務,在 NESTED 情況下聊记,父事務回滾時撒妈,子事務也會回滾,而 REQUIRED_NEW 情況下排监,原有事務回滾狰右,不會影響新開啟的事務

NESTED 和 REQUIRED 的區(qū)別:

REQUIRED 情況下,調用方存在事務時舆床,則被調用方和調用方使用同一個事務棋蚌,那么被調用方出現(xiàn)異常時,由于共用一個事務挨队,所以無論是否 catch異常谷暮,事務都會回滾,而在 NESTED 情況下盛垦,被調用方發(fā)生異常時湿弦,調用方可以 catch其異常,這樣只有子事務回滾腾夯,父事務不會回滾

Spring事務的實現(xiàn)原理

    在使用Spring框架的時候颊埃,可以有兩種事務的實現(xiàn)方式,一種是編程式事務蝶俱,有用內(nèi)自己通過代碼來撐制事務的處理邏輯竟秫,還有一種是聲明式事務,通過 `@Transactional` 注解來實現(xiàn)跷乐。

    其實事務的操作本來應該是由數(shù)據(jù)庫來進行控制,但是為了方便用戶進行業(yè)務邏輯的操作趾浅,Spring對事務功能進行了擴展實現(xiàn)愕提,一般我們很少會用編程式事務,更多的是通過添加 @Transactional 注解來進行實現(xiàn)皿哨,當添加此注解之后事務的自動功能就會關閉浅侨,有Spring框架來幫助進行控制。

    其實事務操作是AOP的一個核心體現(xiàn)证膨,當一個方法添加 @Transactional 注解之后如输,Spring會基于這個類生成個代理對象,會將這個代理對象作為bean,當使用這個代理對象的方法的時候不见,如果有事務處理澳化,那么會先把事務的自動提交給關系,然后去執(zhí)行具體的業(yè)務邏輯稳吮,如果執(zhí)行邏輯沒有出現(xiàn)異常缎谷,那么代理邏輯就會直接提交如果出現(xiàn)任何異常情況,那么直接進行回滾操作灶似,當然用戶可以控制對哪些異常進行回滾操作列林。

Spring事務失效場景

  • bean對象沒有被Spring容器管理

  • 方法的訪間修飾符不是public

  • 切點是否配置正確

  • 自身調用問題

    因為this不是代理對象,可以配置 expose-proxy="true"酪惭,就可以通過 AopContext.currentProx()獲取
    到當前類的代理對象

    <!-- expose-proxy="true" 類內(nèi)部可以獲取到當前類的代理對象 -->
    <aop:aspectj-autoproxy expose-proxy="true"/>
    
    @EnableAspectJAutoProxy(exposeProxy = true)
    

    也可以注入當前bean

  • 數(shù)據(jù)源沒有配置事務管理器

  • 數(shù)據(jù)庫不支持事務

  • 導常被捕獲

  • 異常類型錯誤或者配置錯誤

    默認只支持RuntimeException 和 Error希痴,不支持檢查異常

    想要支持檢查一異常需配置rollbackFor

    @Transactional(rollbackFor = Exception.class)
    
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市春感,隨后出現(xiàn)的幾起案子砌创,更是在濱河造成了極大的恐慌,老刑警劉巖甥厦,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纺铭,死亡現(xiàn)場離奇詭異,居然都是意外死亡刀疙,警方通過查閱死者的電腦和手機舶赔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谦秧,“玉大人竟纳,你說我怎么就攤上這事【卫穑” “怎么了锥累?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長集歇。 經(jīng)常有香客問我桶略,道長,這世上最難降的妖魔是什么诲宇? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任际歼,我火速辦了婚禮,結果婚禮上姑蓝,老公的妹妹穿的比我還像新娘鹅心。我一直安慰自己,他們只是感情好纺荧,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布旭愧。 她就那樣靜靜地躺著颅筋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪输枯。 梳的紋絲不亂的頭發(fā)上议泵,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音用押,去河邊找鬼肢簿。 笑死,一個胖子當著我的面吹牛蜻拨,可吹牛的內(nèi)容都是我干的池充。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼缎讼,長吁一口氣:“原來是場噩夢啊……” “哼收夸!你這毒婦竟也來了?” 一聲冷哼從身側響起血崭,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤卧惜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夹纫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咽瓷,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年舰讹,在試婚紗的時候發(fā)現(xiàn)自己被綠了茅姜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡月匣,死狀恐怖钻洒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锄开,我是刑警寧澤素标,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站萍悴,受9級特大地震影響头遭,放射性物質發(fā)生泄漏。R本人自食惡果不足惜癣诱,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一任岸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狡刘,春花似錦、人聲如沸困鸥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澜术,卻和暖如春艺蝴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸟废。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工猜敢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盒延。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓缩擂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親添寺。 傳聞我的和親對象是個殘疾皇子胯盯,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容