Spring框架 第8章 聲明式事務
第8章 ?聲明式事務
8.1事務概述
? 1)? 在JavaEE企業級開發的應用領域,為了保證數據的完整性和一致性,必須引入數據庫事務的概念,所以事務管理是企業級應用程序開發中必不可少的技術。
? 2)? 事務就是一組由于邏輯上緊密關聯而合并成一個整體(工作單元)的多個數據庫操作,這些操作要么都執行,要么都不執行。
? 3)?事務的四個關鍵屬性(ACID)
①原子性(atomicity):“原子”的本意是“不可再分”,事務的原子性表現為一個事務中涉及到的多個操作在邏輯上缺一不可。事務的原子性要求事務中的所有操作要么都執行,要么都不執行。
②一致性(consistency):“一致”指的是數據的一致,具體是指:所有數據都處于滿足業務規則的一致性狀態。一致性原則要求:一個事務中不管涉及到多少個操作,都必須保證事務執行之前數據是正確的,事務執行之后數據仍然是正確的。如果一個事務在執行的過程中,其中某一個或某幾個操作失敗了,則必須將其他所有操作撤銷,將數據恢復到事務執行之前的狀態,這就是回滾。
③隔離性(isolation):在應用程序實際運行過程中,事務往往是并發執行的,所以很有可能有許多事務同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。隔離性原則要求多個事務在并發執行過程中不會互相干擾。
④持久性(durability):持久性原則要求事務執行完成后,對數據的修改永久的保存下來,不會因各種系統錯誤或其他意外情況而受到影響。通常情況下,事務對數據的修改應該被寫入到持久化存儲器中。
8.2 Spring事務管理
8.2.1編程式事務管理
? 1)? 使用原生的JDBC API進行事務管理
①獲取數據庫連接Connection對象
②取消事務的自動提交
③執行操作
④正常完成操作時手動提交事務
⑤執行失敗時回滾事務
⑥關閉相關資源
? 2)? 評價
使用原生的JDBC API實現事務管理是所有事務管理方式的基石,同時也是最典型 的編程式事務管理。編程式事務管理需要將事務管理代碼嵌入到業務方法中來控制事務 的提交和回滾。在使用編程的方式管理事務時,必須在每個事務操作中包含額外的事務 管理代碼。相對于核心業務而言,事務管理的代碼顯然屬于非核心業務,如果多個模塊 都使用同樣模式的代碼進行事務管理,顯然會造成較大程度的代碼冗余。
8.2.2 聲明式事務管理
大多數情況下聲明式事務比編程式事務管理更好:它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。
事務管理代碼的固定模式作為一種橫切關注點,可以通過AOP方法模塊化,進而借助Spring AOP框架實現聲明式事務管理。
Spring在不同的事務管理API之上定義了一個抽象層,通過配置的方式使其生效,從而讓應用程序開發人員不必了解事務管理API的底層實現細節,就可以使用Spring的事務管理機制。
Spring既支持編程式事務管理,也支持聲明式的事務管理。
8.2.3 Spring提供的事務管理器
Spring從不同的事務管理API中抽象出了一整套事務管理機制,讓事務管理代碼從特定的事務技術中獨立出來。開發人員通過配置的方式進行事務管理,而不必了解其底層是如何實現的。
Spring的核心事務管理抽象是PlatformTransactionManager。它為事務管理封裝了一組獨立于技術的方法。無論使用Spring的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。
事務管理器可以以普通的bean的形式聲明在Spring IOC容器中。
8.2.4事務管理器的主要實現
? 1)? DataSourceTransactionManager:在應用程序中只需要處理一個數據源,而且通過JDBC存取。
? ?2)? JtaTransactionManager:在JavaEE應用服務器上用JTA(Java Transaction API)進行事務管理
? 3)? HibernateTransactionManager:用Hibernate框架存取數據庫
8.3 測試數據準備
8.3.1 需求
8.3.2 數據庫表
CREATE TABLE book?( ??isbn VARCHAR (50) PRIMARY KEY, ??book_name VARCHAR (100), ??price INT ) ;
CREATE TABLE book_stock?( ??isbn VARCHAR (50) PRIMARY KEY, ??stock INT, ??CHECK (stock > 0) ) ;
CREATE TABLE account?( ??username VARCHAR (50) PRIMARY KEY, ??balance INT, ??CHECK (balance > 0) ) ;
INSERT INTO account (`username`,`balance`) VALUES ('Tom',100000); INSERT INTO account (`username`,`balance`) VALUES ('Jerry',150000);
INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-001','book01',100); INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-002','book02',200); INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-003','book03',300); INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-004','book04',400); INSERT INTO book (`isbn`,`book_name`,`price`) VALUES ('ISBN-005','book05',500);
INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-001',1000); INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-002',2000); INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-003',3000); INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-004',4000); INSERT INTO book_stock (`isbn`,`stock`) VALUES ('ISBN-005',5000); |
8.4 初步實現
? 1)? 配置文件
<!-- 配置事務管理器 --> <bean?id="transactionManager"? class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property?name="dataSource"?ref="dataSource"/> ?? </bean>
<!-- 啟用事務注解 --> <tx:annotation-driven?transaction-manager="transactionManager"/> |
? 2)? 在需要進行事務控制的方法上加注解 @Transactional
8.5 事務的傳播行為
8.5.1 簡介
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,并在自己的事務中運行。
事務的傳播行為可以由傳播屬性指定。Spring定義了7種類傳播行為。
事務傳播屬性可以在@Transactional注解的propagation屬性中定義。
8.5.2 測試? ?1) . 說明
①REQUIRED傳播行為
當bookService的purchase()方法被另一個事務方法checkout()調用時,它默認會在現有的事務內運行。這個默認的傳播行為就是REQUIRED。因此在checkout()方法的開始和終止邊界內只有一個事務。這個事務只在checkout()方法結束的時候被提交,結果用戶一本書都買不了。
②. REQUIRES_NEW傳播行為
表示該方法必須啟動一個新事務,并在自己的事務內運行。如果有事務在運行,就應該先掛起它。
8.5.3 補充
在Spring 2.x事務通知中,可以像下面這樣在<tx:method>元素中設定傳播事務屬性。
8.6 事務的隔離級別
8.6.1 數據庫事務并發問題
假設現在有兩個事務:Transaction01和Transaction02并發執行。
? 1)? 臟讀
①Transaction01將某條記錄的AGE值從20修改為30。
②Transaction02讀取了Transaction01更新后的值:30。
③Transaction01回滾,AGE值恢復到了20。
④Transaction02讀取到的30就是一個無效的值。
? 2)? 不可重復讀
①Transaction01讀取了AGE值為20。
②Transaction02將AGE值修改為30。
③Transaction01再次讀取AGE值為30,和第一次讀取不一致。
? 3)? 幻讀
①Transaction01讀取了STUDENT表中的一部分數據。
②Transaction02向STUDENT表中插入了新的行。
③Transaction01讀取了STUDENT表時,多出了一些行。
8.6.2 隔離級別
數據庫系統必須具有隔離并發運行各個事務的能力,使它們不會相互影響,避免各種并發問題。一個事務與其他事務隔離的程度稱為隔離級別。SQL標準中規定了多種事務隔離級別,不同隔離級別對應不同的干擾程度,隔離級別越高,數據一致性就越好,但并發性越弱。
? 1)? 讀未提交:READ UNCOMMITTED
允許Transaction01讀取Transaction02未提交的修改。
? 2)? 讀已提交:READ COMMITTED
???要求Transaction01只能讀取Transaction02已提交的修改。
? 3)? 可重復讀:REPEATABLE READ
???確保Transaction01可以多次從一個字段中讀取到相同的值,即Transaction01執行期間禁止其它事務對這個字段進行更新。
? 4)? 串行化:SERIALIZABLE
???確保Transaction01可以多次從一個表中讀取到相同的行,在Transaction01執行期間,禁止其它事務對這個表進行添加、更新、刪除操作。可以避免任何并發問題,但性能十分低下。
? 5)? 各個隔離級別解決并發問題的能力見下表
|
臟讀 |
不可重復讀 |
幻讀 |
READ UNCOMMITTED |
有 |
有 |
有 |
READ COMMITTED |
無 |
有 |
有 |
REPEATABLE READ |
無 |
無 |
有 |
SERIALIZABLE |
無 |
無 |
無 |
? 6)? 各種數據庫產品對事務隔離級別的支持程度
|
Oracle |
MySQL |
READ UNCOMMITTED |
× |
√ |
READ COMMITTED |
√(默認) |
√ |
REPEATABLE READ |
× |
√(默認) |
SERIALIZABLE |
√ |
√ |
8.6.3 在Spring中指定事務隔離級別
? 1)? 注解
用@Transactional注解聲明式地管理事務時可以在@Transactional的isolation屬性中設置隔離級別
? 2)? XML
在Spring 2.x事務通知中,可以在<tx:method>元素中指定隔離級別
8.7 觸發事務回滾的異常
8.7.1默認情況
捕獲到RuntimeException或Error時回滾,而捕獲到編譯時異常不回滾。
8.7.2設置途經
? 1)? 注解@Transactional 注解
① rollbackFor屬性:指定遇到時必須進行回滾的異常類型,可以為多個
② noRollbackFor屬性:指定遇到時不回滾的異常類型,可以為多個? 2)? XML
在Spring 2.x事務通知中,可以在<tx:method>元素中指定回滾規則。如果有不止一種異常則用逗號分隔。
8.8 事務的超時和只讀屬性
8.8.1簡介
由于事務可以在行和表上獲得鎖,因此長事務會占用資源,并對整體性能產生影響。如果一個事務只讀取數據但不做修改,數據庫引擎可以對這個事務進行優化。超時事務屬性:事務在強制回滾之前可以保持多久。這樣可以防止長期運行的事務占用資源。
只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務。
8.8.2設置
? 1)? 注解
@Transaction注解? 2)? XML
在Spring 2.x事務通知中,超時和只讀屬性可以在<tx:method>元素中進行指定
8.9 基于XML文檔的聲明式事務配置
<!-- 配置事務切面 --> <aop:config> <aop:pointcut expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" id="txPointCut"/> <!-- 將切入點表達式和事務屬性配置關聯到一起 --> <aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/> </aop:config> <!-- 配置基于XML的聲明式事務 ?--> <tx:advice id="myTx" transaction-manager="transactionManager"> <tx:attributes> <!-- 設置具體方法的事務屬性 --> <tx:method name="find*" read-only="true"/> <tx:method name="get*" read-only="true"/> <tx:method name="purchase" isolation="READ_COMMITTED" no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException" propagation="REQUIRES_NEW" read-only="false" timeout="10"/> </tx:attributes> </tx:advice> |