一、核心概念
1、概念
數據庫事務:數據庫事務( transaction)是訪問並可能操作各種數據項的一個數據庫操作序列,這些操作要么全部執行,要么全部不執行,是一個不可分割的工作單位。
事務概念擴展:事務概念來源於數據庫事務,擴展為事務是一個由有限操作集合組成的邏輯單元,包括文件系統,消息隊列,一組不可分割的方法操作等。
事務操作的目的:
① 數據一致,指事務提交時保證事務內的所有操作都成功完成,並且更改永久生效;事務回滾時,保證能夠恢復到事務執行之前的狀態。
② 操作隔離,指多個同時執行的事務之間應該相互獨立,互不影響。
2、事務的四個特性:ACID
- 原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。
- 隔離性(Isolation):可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
- 持久性(Durability):一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。
並發事務帶來的問題
- 丟失更新(Lost Update): 當兩個或多個事務選擇同一行,最初的事務修改的值,會被后面的事務修改的值覆蓋。
- 臟讀(Dirty Reads): 當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。
- 不可重復讀(NonRepeatable Reads): 一個事務在讀取某些數據后的某個時間,再次讀取以前讀過的數據,卻發現和以前讀出的數據不一致。
- 幻讀(Phantom Reads): 一個事務按照相同的查詢條件重新讀取以前查詢過的數據,卻發現其他事務插入了滿足其查詢條件的新數據。
為了解決上述提到的事務並發問題,數據庫提供一定的事務隔離機制來解決這個問題。MySQL的InnoDB引擎提供四種隔離級別(即ACID中的隔離性)
- 讀未提交(READ UNCOMMITTED),能解決幻讀問題問題;
- 讀已提交(READ COMMITTED),能解決不可重復讀取、幻讀問題;
- 可重復讀(REPEATABLE READ)(默認),能解決臟讀、不可重復讀、幻讀問題;
- 串行化(SERIALIZABLE)
InnoDB默認的隔離級別是REPEATABLE READ
,其可避免臟讀和不可重復讀,但不能避免幻讀。
引申:並發事務控制是通過MVCC(多版本並發控制)實現,MVCC是樂觀鎖的一種實現方式,目的是解決讀寫互斥問題,增大並發程度。mysql中的mvcc通過隱藏列DB_TRX_ID、DB_ROLL_PTR和ReadView實現。DB_TRX_ID是該行記錄當前的事務id,DB_ROLL_PTR指向該行在undo log中的位置,ReadView是一個記錄當前活躍事務id的鏈表。
當一行記錄被修改時,undo文件中會存儲它的相反操作,並且存有修改之前的版本號。每次修改會在undo文件中形成鏈式存儲,鏈表頭部是最近的版本,尾部是最老版本。當事務在執行select操作之前,會先構造ReadView(快照),並通過ReadView判斷當前記錄是否是可見的。ReadView中按序存儲(事務id排序)着所有活躍事務id,如果當前記錄的事務id小於ReadView中的最小值,意味着該記錄已經被提交,則該記錄可見。如果大於ReadView中最大值,那么該記錄應當在之后的事務中提交,因此屬於不可見。如果處於最大最小值之前,則需要通過二分查找判斷,事務id是否存在於ReadView中,如果存在,說明該記錄的事務活躍,但無法判定事務是否已經提交,因此需要讀取DB_ROLL_PTR,獲取上一個版本的事務id,看看上個版本是否是可見的,通過層層遞歸,直到某一個之前的版本滿足可見的要求。
傳送門:https://baijiahao.baidu.com/s?id=1709427910908845097&wfr=spider&for=pc
https://www.cnblogs.com/orangelsk/articles/15807591.html
3、傳播行為(Spring針對方法嵌套調用時事務的創建行為定義了七種事務傳播機制)
事務的第一個方面是傳播行為(propagation behavior)。當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,並在自己的事務中運行。Spring定義了七種傳播行為:
傳播行為 | 含義 |
---|---|
PROPAGATION_REQUIRED |
表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啟動一個新的事務 |
PROPAGATION_SUPPORTS |
表示當前方法不需要事務上下文,但是如果存在當前事務的話,那么該方法會在這個事務中運行 |
PROPAGATION_MANDATORY |
表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常 |
PROPAGATION_REQUIRED_NEW |
表示當前方法必須運行在它自己的事務中。一個新的事務將被啟動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
PROPAGATION_NOT_SUPPORTED |
表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
PROPAGATION_NEVER |
表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常 |
PROPAGATION_NESTED |
表示如果當前已經存在一個事務,那么該方法將會在嵌套事務中運行。嵌套的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那么其行為與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行為的支持是有所差異的。可以參考資源管理器的文檔來確認它們是否支持嵌套事務 |
4、事務執行原理(以mysql innodb引擎為例)
1). redo log
redo log 在InnoDB存儲引擎中產生的,是用來實現事務的原子性和持久性。該日志文件由兩部分組成:重做日志緩沖(redo log buffer)以及重做日志文件(redo log),前者是在內存中,后者在磁盤中。當事務提交之后會把所有修改信息都會存到該日志中,也就是對磁盤上的數據進行的修改操作,用於在刷新臟頁到磁盤發生錯誤時, 進行數據恢復使用(Redo Log往往用來恢復提交后的物理數據頁,不過只能恢復到最后一次提交的位置)。
start transaction; select balance from bank where name="Tom"; -- 生成 重做日志 balance=8000 update bank set balance = balance - 2000; -- 生成 重做日志 account=2000 update finance set account = account + 2000; commit;
執行流程如圖所示:
mysql 為了提升性能不會把每次的修改都實時同步到磁盤,而是會先存到Buffer Pool(緩沖池)里 頭,把這個當作緩存來用。然后使用后台線程將緩存池刷新到磁盤。 當在執行刷新時,宕機或者斷電,可能會丟失部分數據。所以引入了redo log來記錄已成功提交事務的修改信息,並且在事務提交時會把redo log持久化到磁盤,系統重啟之后在讀取redo log恢復最新數據。 簡單來說 , redo log是用來恢復數據的用於保障,已提交事務的持久化特性 ;
2). undo log
undo log 即回滾日志,記錄數據被修改前的信息。用於回滾事務和多版本並發事務。正好跟前面所說的重做日志所記錄的相反,重做日志記錄數據被修改后的信息。undo log主要記錄的是數據的邏輯變化,為了在發生錯誤時回滾到之前的操作,需要將之前的操作都記錄下來,然后在發生錯誤時才可以回滾。
undo log 記錄事務修改之前版本的數據信息,因此假如由於系統錯誤或者rollback操作而回滾的話,可以根據undo log的信息來進行回滾到沒被修改前的狀態。
在MySQL啟動事務之前,會先將要修改的數據記錄存儲到Undo Log中。如果數據庫的事務回滾或者MySQL數據庫崩潰,可以利用Undo Log對數據庫中未提交的事務進行回滾操作,從而保證數據庫中數據的一致性。Undo Log會在事務開始前產生,當事務提交時,並不會立刻刪除相應的Undo Log。此時,InnoDB存儲引擎會將當前事務對應的Undo Log放入待刪除的列表,接下來,通過一個后台線程purgethread進行刪除處理。
3)redo log和undo log區別
Undo Log與Redo Log不同,Undo Log記錄的是邏輯日志,可以這樣理解:當數據庫執行一條insert語句時,Undo Log會記錄一條對應的delete語句;當數據庫執行一條delete語句時,UndoLog會記錄一條對應的insert語句;當數據庫執行一條update語句時,Undo Log會記錄一條相反的update語句。
需要注意的是,因為MySQL事務執行過程中產生的Undo Log也需要進行持久化操作,所以UndoLog也會產生Redo Log。由於Undo Log的完整性和可靠性需要Redo Log來保證,因此數據庫崩潰時需要先做Redo Log數據恢復,然后做Undo Log回滾。
5、spring事務原理
純JDBC操作數據庫事務步驟:
- 獲取連接 Connection con = DriverManager.getConnection()
- 開啟事務con.setAutoCommit(true/false);
- 執行CRUD
- 提交事務/回滾事務 con.commit() / con.rollback();
- 關閉連接 conn.close();
使用Spring的事務管理功能后,步驟 2 和 4 的代碼,而是由Spirng 自動完成。以注解方式為例,配置文件開啟注解驅動,在相關的類和方法上通過注解@Transactional標識。Spring 在啟動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,並且為這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中增加了事務代碼邏輯(開啟正常提交事務,異常回滾事務)。而實質實現事務功能是通過數據庫的事務處理(見上文,4、事務執行原理)。
二、應用場景
1、JDBC事務
在JDBC中處理事務,都是通過Connection完成的。同一事務中所有的操作,都在使用同一個Connection對象。
Connection的三個方法與事務有關:
- setAutoCommit(boolean):設置是否為自動提交事務,如果true(默認值為true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設置為false,那么相當於開啟了事務了;
- commit():提交結束事務。
- rollback():回滾結束事務。
JDBC處理事務的代碼格式:
try{ con.setAutoCommit(false);//開啟事務 ...... con.commit();//try的最后提交事務 } catch() { con.rollback();//回滾事務 }
eg:
import cn.itcast.jdbc.JdbcUtils; import org.junit.Test; import java.sql.Connection; import java.sql.SQLException; public class Demo1 { /* * 演示轉賬方法 * 所有對Connect的操作都在Service層進行的處理 * 把所有connection的操作隱藏起來,這需要使用自定義的小工具(day19_1) * */ public void transferAccounts(String from,String to,double money) { //對事務的操作 Connection con = null; try{ con = JdbcUtils.getConnection(); con.setAutoCommit(false); AccountDao dao = new AccountDao(); dao.updateBalance(con,from,-money);//給from減去相應金額 if (true){ throw new RuntimeException("不好意思,轉賬失敗"); } dao.updateBalance(con,to,+money);//給to加上相應金額 //提交事務 con.commit(); } catch (Exception e) { try { con.rollback(); } catch (SQLException e1) { e.printStackTrace(); } throw new RuntimeException(e); } } @Test public void fun1() { transferAccounts("zs","ls",100); } }
補充:
JDBC事務優缺點:JDBC為使用Java進行數據庫的事務操作提供了最基本的支持。通過JDBC事務,我們可以將多個SQL語句放到同一個事務中,保證其ACID特性。但是,一個 JDBC 事務不能跨越多個數據庫,不支持多數據庫的操作或分布式場景。
2、JTA事務
JTA(Java Transaction API)是一種高層的,與實現無關的,與協議無關的API,應用程序和應用服務器可以使用JTA來訪問事務。JTA提供了跨數據庫連接的事務管理能力。JTA事務管理則由JTA容器實現。一個分布式事務包括一個事務管理器和一個或多個資源管理器。它是XA協議(采用兩階段提交方式來管理分布式事務)的Java版本規范,JavaEE的13個規范之一。
1)JTA的構成
a、高層應用事務界定接口,供事務客戶界定事務邊界的;
b、X/Open XA協議(資源之間的一種標准化的接口)的標准Java映射,它可以使事務性的資源管理器參與由外部事務管理器控制的事務中;
c、高層事務管理器接口,允許應用程序服務器為其管理的應用程序界定事務的邊界;
2)JTA是基於Java語言開發分布式事務二階段提交的標准。而要想真正的使用,必須使用它的實現。JavaEE容器技術(也有稱為服務器或中間件的),如Tomcat,weblogic ,websphere, JBOSS,Jetty,Resin等等。這里面有輕量級,也有重量級。凡是重量級服務器都是完整實現了JavaEE規范的,也就是說可以直接使用JTA的實現。除了完整的實現了JavaEE規范的容器外,還有單獨針對JTA進行實現的,像 atomikos,JOTM。
3、容器事務:主要指的是J2EE應用服務器提供的事務管理,如在Spring、Hibernate等框架中都有各自的事務管理功能,表現形式不同,但都是在JAVA事務管理的基礎上實現的。
Spring事務
Spring並不直接管理事務,而是提供了多種事務管理器,從本質上講,Spring事務是對數據庫事務的進一步封裝。也就是說,如果數據庫不支持事務,Spring也無法實現事務操作。他們將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平台框架的事務來實現。
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過這個接口,Spring為各個平台如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平台自己的事情了。此接口的內容如下:
Public interface PlatformTransactionManager()...{ // 由TransactionDefinition得到TransactionStatus對象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滾 Void rollback(TransactionStatus status) throws TransactionException; }
所以Spring事務管理的一個優點就是為不同的事務API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。
Spring事務抽象的核心類圖
部分Spring包含的對PlatformTransactionManager
的實現類如下圖所示:
AbstractPlatformTransactionManager
抽象類實現了Spring事務的標准流程,其子類DataSourceTransactionManager
是我們使用較多的JDBC單數據源事務管理器,而JtaTransactionManager
是JTA(Java Transaction API)規范的實現類,另外兩個則分別是JavaEE容器WebLogic和WebSphere的JTA事務管理器的具體實現。
spring事務核心邏輯
事務攔截器TransactionInterceptor
在invoke
方法中,通過調用父類TransactionAspectSupport
的invokeWithinTransaction
方法進行事務處理,該方法支持聲明式事務和編程式事務。
// TransactionInterceptor.class @Override public Object invoke(final MethodInvocation invocation) throws Throwable { // 獲取targetClass ... // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { @Override public Object proceedWithInvocation() throws Throwable { // 實際執行目標方法 return invocation.proceed(); } }); } // TransactionInterceptor父類TransactionAspectSupport.class protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. // 查詢目標方法事務屬性、確定事務管理器、構造連接點標識(用於確認事務名稱) final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 事務獲取 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // 通過回調執行目標方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 目標方法執行拋出異常,根據異常類型執行事務提交或者回滾操作 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { // 清理當前線程事務信息 cleanupTransactionInfo(txInfo); } // 目標方法執行成功,提交事務 commitTransactionAfterReturning(txInfo); return retVal; } else { // 帶回調的事務執行處理,一般用於編程式事務 ... } }
TransactionAspectSupport
//TransactionAspectSupport.class protected TransactionInfo createTransactionIfNecessary( PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { ... TransactionStatus status = null; if (txAttr != null) { if (tm != null) { // 獲取事務 status = tm.getTransaction(txAttr); ... } protected void commitTransactionAfterReturning(TransactionInfo txInfo) { if (txInfo != null && txInfo.hasTransaction()) { ... // 提交事務 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } } protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.hasTransaction()) { ... if (txInfo.transactionAttribute.rollbackOn(ex)) { try { // 異常類型為回滾異常,執行事務回滾 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } ... } else { try { // 異常類型為非回滾異常,仍然執行事務提交 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } ... } protected final class TransactionInfo { private final PlatformTransactionManager transactionManager; ...
1)spring-jdbc
如果應用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會處理事務邊界。為了使用DataSourceTransactionManager,需要使用如下的XML將其裝配到應用程序的上下文定義中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
實際上,DataSourceTransactionManager是通過調用java.sql.Connection來管理事務,而后者是通過DataSource獲取到的。通過調用連接的commit()方法來提交事務,同樣,事務失敗則通過調用rollback()方法進行回滾。
2)Hibernate事務
如果應用程序的持久化是通過Hibernate實現的,那么需要使用HibernateTransactionManager。對於Hibernate3,需要在Spring上下文定義中添加如下的<bean>
聲明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職責委托給org.hibernate.Transaction對象,而后者是從Hibernate Session中獲取到的。當事務成功完成時,HibernateTransactionManager將會調用Transaction對象的commit()方法,反之,將會調用rollback()方法。
3)Java持久化API事務(JPA)
Hibernate多年來一直是事實上的Java持久化標准,但是現在Java持久化API作為真正的Java持久化標准進入大家的視野。如果使用JPA的話,需要使用Spring的JpaTransactionManager來處理事務。需要在Spring中這樣配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
4)Java原生API事務
如果沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(比如兩個或者是多個不同的數據源),就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:TransactionManager" /> </bean>
JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現)。JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。
三、Spring事務應用---編程式事務和聲明式事務
區別:Spring提供了對編程式事務和聲明式事務的支持,編程式事務允許用戶在代碼中精確定義事務的邊界,而聲明式事務(基於AOP)有助於用戶將操作與事務規則進行解耦(對原有代碼無侵入)。
(1)編程式事務
Spring提供兩種方式的編程式事務管理,分別是:使用TransactionTemplate和直接使用PlatformTransactionManager。
1.1)使用TransactionTemplate
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一樣的方法。它使用回調方法,把應用程序從處理取得和釋放資源中解脫出來。如同其他模板,TransactionTemplate是線程安全的。代碼片段:
TransactionTemplate tt = new TransactionTemplate(); // 新建一個TransactionTemplate Object result = tt.execute( new TransactionCallback(){ public Object doTransaction(TransactionStatus status){ updateOperation(); return resultOfUpdateOperation(); } }); // 執行execute方法進行事務管理
使用TransactionCallback()可以返回一個值。如果使用TransactionCallbackWithoutResult則沒有返回值。
1.2)使用PlatformTransactionManager
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定義一個某個框架平台的TransactionManager,如JDBC、Hibernate dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設置數據源 DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務屬性 transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設置傳播行為屬性 TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務狀態 try { // 數據庫操作 dataSourceTransactionManager.commit(status);// 提交 } catch (Exception e) { dataSourceTransactionManager.rollback(status);// 回滾 }
(2)聲明式事務
①聲明式事務原理
聲明式事務的實現就是通過環繞增強的方式,在目標方法執行之前開啟事務,在目標方法執行之后提交或者回滾事務,事務攔截器的繼承關系圖可以體現這一點:
②用法:根據代理機制的不同,總結了五種Spring事務的配置方式,配置文件如下:
1)每個Bean都有一個代理
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定義事務管理器(聲明式的事務) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 配置DAO --> <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 配置事務管理器 --> <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="userDaoTarget" /> <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" /> <!-- 配置事務屬性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> </beans>
2)所有Bean共享一個代理基類
3)使用攔截器
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定義事務管理器(聲明式的事務) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager" /> <!-- 配置事務屬性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Dao</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> <!-- 配置DAO --> <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
4)使用tx標簽配置的攔截器
5)全注解
<?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 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.bluesky" /> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定義事務管理器(聲明式的事務) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
此時在DAO上需加上@Transactional注解,如下:
import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.stereotype.Component; @Transactional @Component("userDao") public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public List<User> listUsers() { return this.getSession().createQuery("from User").list(); } }
eg:
數據庫表:
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
xml配置:
<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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <import resource="applicationContext-db.xml" /> <context:component-scan base-package="com.springinaction.transaction"> </context:component-scan> <tx:annotation-driven transaction-manager="txManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
BookShopServiceImpl
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; /** * 1.添加事務注解 * 使用propagation 指定事務的傳播行為,即當前的事務方法被另外一個事務方法調用時如何使用事務。 * 默認取值為REQUIRED,即使用調用方法的事務 * REQUIRES_NEW:使用自己的事務,調用的事務方法的事務被掛起。 * * 2.使用isolation 指定事務的隔離級別,最常用的取值為READ_COMMITTED * 3.默認情況下 Spring 的聲明式事務對所有的運行時異常進行回滾,也可以通過對應的屬性進行設置。通常情況下,默認值即可。 * 4.使用readOnly 指定事務是否為只讀。 表示這個事務只讀取數據但不更新數據,這樣可以幫助數據庫引擎優化事務。若真的是一個只讀取數據庫值得方法,應設置readOnly=true * 5.使用timeOut 指定強制回滾之前事務可以占用的時間。 */ @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, noRollbackFor={UserAccountException.class}, readOnly=true, timeout=3) @Override public void purchase(String username, String isbn) { //1.獲取書的單價 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新書的庫存 bookShopDao.updateBookStock(isbn); //3.更新用戶余額 bookShopDao.updateUserAccount(username, price); } }
備注:和spring boot集成使用方式參考 https://zhuanlan.zhihu.com/p/227922586
四、分布式事務
1、核心概念
(1)分布式事務是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分布式系統的不同節點之上。簡單來說就是組成事務的各個單元處於不同數據庫服務器上。 在我們的實際開發中,分布式事務無處不在,比如,電商系統中的生成訂單,賬戶扣款,減少庫存,增加會員積分等等,他們就是組成事務的各個單元,它們要么全部發生,要么全部不發生,從而保證最終一致,數據准確。
(2)剛性事務和柔性事務:
剛性事務指的就是遵循本地事務四大特性(ACID)的強一致性事務。它的特點就是強一致性,要求組成事務的各個單元馬上提交或者馬上回滾,沒有時間彈性,要求以同步的方式執行。通常在單體架構項目中應用較多,一般都是企業級應用(或者局域網應用)。例如:生成合同時記錄日志,付款成功后生成憑據等等。
柔性事務是針對剛性事務而說的,剛性事務的兩個特點,強一致性和近實時性 。而柔性事務的特點是不需要立刻馬上執行(同步性),且不需要強一致性。它只要滿足基本可用和最終一致就可以了。即根據每個業務根據自身的特點,采用適當的方式來使系統達到最終一致性。
2、5種分布式事務解決方案原理(推薦文章 https://developer.51cto.com/art/201907/600249.htm)
1)2pc(兩階段提交協議)
兩階段,即准備階段(投票階段) 和 提交/回滾階段(執行階段)。確定是存在節點同步阻塞問題,單點故障情況下參與者存在阻塞問題,可以通過補償機制避免這些問題影響。
2)3pc(三階段提交協議)
三階段提交協議是對二階段提交協議的補充,彌補了二階段提交協議的不足,增加了canCommit階段,減少了不必要的資源浪費,這個階段只校驗sql,不執行事務,不占用資源。同時,3pc協議在協調者和參與者中引入了超時機制。
3)TCC(補償性事務)
即try(業務檢查階段)-confirm(業務確認階段)-cancal(業務回滾階段),一種常用的分布式事務解決方案;
4)本地消息表(可靠消息最終一致性方案)
事務發起方執行完本地事務,發動一條消息,事務參與方一定能夠接收消息並可以處理自己的事務。
(1)本地消息表
這種實現方式應該是業界使用最多的,其核心思想是將分布式事務拆分成本地事務進行處理,這種思路是來源於ebay。它和MQ事務消息的實現思路都是一樣的,都是利用MQ通知不同的服務實現事務的操作。不同的是,針對消息隊列的信任情況,分成了兩種不同的實現。本地消息表它是對消息隊列的穩定性處於不信任的態度,認為消息可能會出現丟失,或者消息隊列的運行網絡會出現阻塞,於是在數據庫中建立一張獨立的表,用於存放事務執行的狀態,配合消息隊列實現事務的控制。
(2)MQ事務消息
有一些第三方的MQ是支持事務消息的,比如RocketMQ,ActiveMQ,他們支持事務消息的方式也是類似於采用的二階段提交。 以RocketMQ中間件為例,其思路大致為:
第一階段Prepared消息,會拿到消息的地址。
第二階段執行本地事務。
第三階段通過第一階段拿到的地址去訪問消息,並修改狀態。
也就是說在業務方法內要想消息隊列提交兩次請求,一次發送消息和一次確認消息。如果確認消息發送失敗了 RocketMQ會定期掃描消息集群中的事務消息,這時候發現了Prepared消息,它會向消息發送者確認,所以生產方需要實現一個check接口,RocketMQ會根據發送端設置的策略來決定是回滾還是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。
RocketMQ事務消息的執行流程圖
RocketMQ應用示例:
5)Sagas事務模型(最終一致性)
Saga模式是一種分布式異步事務,一種最終一致性事務,是一種柔性事務。
4、分布式事務框架-Seata
(1)簡介
Seata 是一款開源的分布式事務解決方案,致力於提供高性能和簡單易用的分布式事務服務。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務4種模式,為用戶打造一站式的分布式解決方案。
在圖中的三個和seata相關的組件,他們分別是:
1)事務協調器(TC):維護全局和分支事務的狀態,驅動全局提交或回滾。
2)事務管理器(TM):定義全局事務的范圍:開始全局事務,提交或回滾全局事務。
3)資源管理器(RM):管理分支事務的資源,與TC通信以注冊分支事務和報告分支事務的狀態,並驅動分支事務 提交或回滾。
執行流程如下:
1.TM要求TC開始新的全局事務。 TC生成表示全局事務的XID。
2.XID通過微服務的調用鏈傳播。
3.RM將本地事務注冊為XID到TC的相應全局事務的分支。
4.TM要求TC提交或回滾XID的相應全局事務。
5.TC在XID的相應全局事務下驅動所有分支事務,以完成分支提交或回滾。
(2)Seata AT 模式
兩階段提交協議的演變:
-
一階段:業務數據和回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源。
-
二階段:
- 提交異步化,非常快速地完成。
- 回滾通過一階段的回滾日志進行反向補償。
(3)TCC模式
TCC模式又稱為補償型分布式事務解決方案,它是通過開發者自己編寫補償代碼來實現事務各個組成部分的最終一致性的。其原理圖大致如下:
(3)、ShardingSphere 集成了 SEATA作為分布式事務解決方案
Apache ShardingSphere ,是一套開源的分布式數據庫解決方案組成的生態圈,包括由JDBC、Proxy 和 Sidecar(規划中)。TCC 和 Saga 是兩種常見分布式事務實現方案, 主張開發者自行實現對數據庫的反向操作,來達到數據在回滾時仍能夠保證最終一致性。 SEATA 實現了 SQL 反向操作的自動生成,可以使柔性事務不再必須由開發者介入才能使用。ShardingSphere 集成了 SEATA 作為柔性事務的使用方案。
5、分布式事務框架-Atomikos(不推薦)
atomikos一套開源的,JTA規范的實現。它是基於二階段提交思想實現分布式事務的控制。是分布式剛性事務的一種解決方案。通常情況下使用2pc提交方案的場景都是單服務多數據源(多數據庫)的情況。執行原理如圖所示:
6、分布式事務解決方案特點
(1)TCC方案
特點:嚴格一致性 執行時間短 實時性要求高
適用場景:搶紅包 實時轉賬匯款 收付款
(2)異步確保方案
特點:實時性不高 執行周期較長
適用場景:非實時匯款 退貨退款業務
(3)最大努力通知方案
特點:高並發低耦合 不支持回滾
適用場景:獲取交易結果(例如:共享單車支付等)
感謝閱讀,借鑒了不少大佬資料,如需轉載,請注明出處,謝謝!https://www.cnblogs.com/huyangshu-fs/p/15600093.html