項目中用到了spring的事務:
@Transactional(rollbackFor = Exception.class, transactionManager = "zebraTransactionManager0") @Override public int acceptAppeal(AppealDetourDataDto dataDto) { int affectedRows = 0; AppealDetourDO appealDetourDO = this.buildAppealDetourDo(dataDto); affectedRows = appealDetourDO.create(); MetricHelper.build().name("appealDetour.accept").count(); return affectedRows; }
查了一下聲明式事務,注解的方式是如何使用的;
事務管理是企業級應用程序開發中必備技術,用來確保數據的完整性和一致性。本文主要講解事務涉及到一些概念以及spring中事務的使用。如有理解偏頗之處,懇請各位大神指正,小編不勝感激!
1、何為事務?
事務就是把一系列的動作當成一個獨立的工作單元,這些動作要么全部完成,要么全部不起作用。就是把一系列的操作當成原子性去執行。
事務四個屬性ACID
1、原子性(atomicity)
事務是原子性操作,由一系列動作組成,事務的原子性確保動作要么全部完成,要么完全不起作用
2、一致性(consistency)
一旦所有事務動作完成,事務就要被提交。數據和資源處於一種滿足業務規則的一致性狀態中
3、隔離性(isolation)
可能多個事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞
4、持久性(durability)
事務一旦完成,無論系統發生什么錯誤,結果都不會受到影響。通常情況下,事務的結果被寫到持久化存儲器中
2、Spring中的事務管理
Spring在不同的事務管理API之上定義了一個抽象層,使得開發人員不必了解底層的事務管理API就可以使用Spring的事務管理機制。Spring支持編程式事務管理和聲明式的事務管理。
編程式事務管理
將事務管理代碼嵌到業務方法中來控制事務的提交和回滾
缺點:必須在每個事務操作業務邏輯中包含額外的事務管理代碼
聲明式事務管理
一般情況下比編程式事務好用。將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。將事務管理作為橫切關注點,通過aop方法模塊化。Spring中通過Spring AOP框架支持聲明式事務管理。
3、事務管理器
Spring的核心事務管理抽象,管理封裝了一組獨立於技術的方法,無論使用Spring的哪種事務管理策略(編程式或者聲明式)事務管理器都是必須的。
org.springframework.transaction.PlatformTransactionManager
JDBC事務
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Hibernate事務
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JPA事務
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Java原生API事務
通常用於跨越多個事務管理源(多數據源),則需要使用下面的內容
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
4、事務屬性
4.1傳播行為
事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如方法可能繼續在現有事務中運行,也可能開啟一個新的事務,並在自己的事務運行。spring中的事務傳播行為可以由傳播屬性指定。spring指定了7種類傳播行為。
REQUIRED 如果有事務在運行,當前的方法就在這個事務內運行,否則就開啟一個新的事務,並在自己的事務內運行,默認傳播行為
REQUIRED_NEW 當前方法必須啟動新事務,並在自己的事務內運行,如果有事務正在運行,則將它掛起
SUPPORTS 如果有事務在運行,當前的方法就在這個事務內運行,否則可以不運行在事務中
NOT_SUPPORTED
表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager
MANDATORY 當前的方法必須運行在事務內部,如果沒有正在運行的事務,就會拋出異常
NEVER 當前方法不應該運行在事務中,如果有運行的事務,就拋出異常
NESTED 如果有事務在運行,當前的方法就應該在這個事務的嵌套事務內運行。嵌套的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那么其行為與PROPAGATION_REQUIRED一樣。
通常情況下,第一種和第二種用的比較多,需要多理解一下
4.2事務隔離級別
並發事務會導致發生以下三種類型的問題
臟讀 發生在一個事務讀取了另一個事務改寫尚未提交的數據時,改寫的數據被回滾了,那么第一個事務獲取的數據無效
不可重復讀 當同一個事務執行兩次及以上相同的查詢時,每次都得到不同的數據。一般因為另一並發事務在兩次查詢期間進行了更新
幻讀 第一個事務讀取了一些數據,此時第二個事務在該表中插入了一些新數據,這是第一個事務再讀取相同的數據就會多幾行
不可重復讀和幻讀的區別:不可重復讀側重點在相同數據被修改,而幻讀是刪除或新增
從理論上講,事務應該完全隔離,避免並發事務導致的問題,但是這樣可能對性能產生極大影響,因為事務必須按順序進行了。所以在實際的開發中,為了提升性能,事務會以比較低的隔離級別運行。
spring中事務的隔離級別可以通過隔離屬性指定
DEFAULT 使用底層數據庫的默認隔離級別,大部分數據庫,默認隔離級別都是READ_COMMITED
READ_COMMITED 只允許事務讀取已經被其他事務提交的更改,可以避免臟讀,但不可重復讀和幻讀問題仍然可能出現
READ_UNCOMMITED 允許事務讀取未被其他事務提交的更改。臟讀,不可重復讀,幻讀都可能會出現
REPEATABLE_READ
確保事務可以多次從一個字段中讀取相同的值。在這個事務持續期間,禁止其他事務對這個字段進行更新
,可以避免臟讀和不可重復讀,但是幻讀的問題依然存在
SERIALIZABLE
確保事務可以從一個表中讀取相同的行,在這個事務持續期間,禁止其他事務對該表執行插入,更新,刪除。所有的並發問題都能避免,但是性能比較低。
注意:事務的隔離級別需要底層數據庫引擎的支持,而不是應用程序或者框架的支持
Oracle支持2種事務隔離級別:READ_COMMITED,SERIALIZABLE
MySQL支持4種事務隔離級別
4.3回滾規則
默認情況下只有未檢查異常(RuntimeException和Error類型的異常)會導致事務回滾。事務的回滾規則可以通過屬性管理
rollbackFor:遇到時必須進行回滾
noRollbackFor:一組異常類,遇到時必須不能回滾
4.4只讀屬性
如果事務只讀數據但不修改可以通過配置只讀事務屬性,幫助數據庫引擎優化事務。只讀事務屬性:表示這個事務只讀讀取數據,但是不更新數據
4.5超時事務屬性
事務可以在行和表上獲得鎖,因此長事務會占用資源,並對整體性能產生影響。可以配置超時事務屬性,事務在強制回滾之前可以保持多久,這樣可以避免長期運行的事務占用資源
5、聲明式管理事務
5.1通知聲明式地管理事務
<!-- 1. 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事務屬性 -->
<!--<tx:advice>元素聲明事務通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根據方法名指定事務的屬性 -->
<tx:method name="*"/>
<!--propagation配置事務傳播行為-->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<!--isolation配置事務的隔離級別-->
<tx:method name="update*" isolation="SERIALIZABLE"/>
<!--rollback-for配置事務遇到異常必須回滾,no-rollback-for配置事務遇到異常必須不能回滾-->
<tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException"/>
<!--read-only配置事務只讀屬性-->
<tx:method name="find*" read-only="true"/>
<!--timeout配置事務的超時屬性-->
<tx:method name="get*" timeout="3"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事務切入點, 以及把事務切入點和事務屬性關聯起來 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
5.2用 @Transactional 注解聲明式地管理事務
Spring中注解的方式@Transactional標注事務方法。為了將方法定義為支持事務處理,可以在方法上添加@Transactional注解。根據Spring AOP基於代理機制,只能標注公有方法。如果在類上標注@Transactional注解,那么這個類中所有公有方法都會被定義為支持事務。
<!-- 配置事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 啟用事務注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在方法上添加
//添加事務注解
//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},
rollbackFor = IOException.class,
readOnly=false,
timeout=3)
@Override
public void purchase(String username, String isbn) {}
6、事務失效
因為spring事務是基於aop的代理機制,當方法中調用this本身的方法時候即使在this的方法標明事務注解,但是事務注解會失效。如下
@Transactional
@Override
public void purchase(String username, String isbn) {
this.update(username, isbn);
}
@Transactional
public void update(String username, String isbn) {
//1. 獲取書的單價
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新數的庫存
bookShopDao.updateBookStock(isbn);
//3. 更新用戶余額
bookShopDao.updateUserAccount(username, price);
}
原因:因為調用ths本身方法不走代理機制,這個時候可以通過配置解決這個問題。
解決事務失效
在配置中添加如下內容
<!--開啟aspectj代理,並暴露aop代理到ThreadLocal-->
<aop:aspectj-autoproxy expose-proxy="true"/>
將上述調用的地方改成如下
@Transactional
@Override
public void purchase(String username, String isbn) {
((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
}
總結
事務在項目開發過程非常重要,涉及到數據的一致性的問題,不容馬虎!