使用Spring管理數據庫事務
- 2020 年 4 月 6 日
- 筆記
在整個JavaWeb項目開發中,事務是用來開發可靠性網絡應用程序的最關鍵部分。當應用程序與後端資源進行交互時,就會用到事務,這裡的後端資源包括數據庫、MQ、ERP等。而數據庫事務是最常見的類型,而我們常說的事務也就是狹義上的與關係型數據庫交互的事務。
事務主要分為本地事務和全局事務。全局事務又稱分佈式事務,本地事務就是當應用程序連接單個數據庫資源時的事務,也是本文化主要討論的內容。
一、事務的一些基本概念
事務的屬性(ACID):
- 原子性
- 一致性
- 隔離性
- 持久性
白話「事務」
事務有三個狀態(或者說是過程):開始、提交、回滾。
假設有這麼一個場景:張三和李四各有100元,有一天,張三要給李四轉10元。
相當於目前的微信轉賬,張三給李四發了10元的轉賬。有以下三種狀態
上邊這個例子有一處不恰當的地方就是,就算李四沒有操作這10元時,張三已經少了10元,這一點和事務有出入 ,我們就假裝如果李四不接收或者退回這10元,張三的微信錢包里還有100元。但是在微信中有那麼多的人相互轉賬,每一次轉賬就是一個事務,我們就要把這些事務進行隔離,但是它有不同的隔離級別(見下)
事務的隔離級別
隔離級別 | 描述 | 舉例 |
---|---|---|
DEFAULT | 底層數據庫存儲的默認隔離級別 | |
READ_UNCOMMITTED | 最低的隔離級別,可以說它並不是事務,因為它允許其他事務來讀取未來提交的數據 | 上邊的例子中,就算李四沒有收這10元,其他人也能讀取到李四多了10元。 |
READ_COMMITTED | 大多數數據庫的默認級別,它確保其他事務可以讀取其他事務已經提交的數據 | 只有當李四對這10元進行操作(接收或者退回)時,別人才能看到這兩個的餘額變化。 |
REPEATABLE_READ | 比上一個更為嚴格,它確保在選擇了數據後,如果其他事務對這個數據進行了更改,就可以選擇新的數據。 | 上邊的是在轉賬過程中,就算別人給張三又轉了10元,在這個事務提交前,張三一直認為自己只有100元。但是這個類型中,張三在轉賬過程中,可以查到自己有110元 |
SERIALIZABLE | 可序列化,是最嚴格最可靠的隔離級別,讓所有事務一個接着一個地運行 | 系統讓每個人轉賬事務一個一個地執行,就不會有任何錯誤了(當然,這裡的事務不單單指轉賬這一個事務) |
事務的傳播類型
也就是當前事務開始的機制和時間,相當於這麼多的人之間的微信轉賬應該怎麼進行
傳播類型 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中; |
PROPAGATION_SUPPORTS | 支持當前事務,如果當前沒有事務,就以非事務方式執行; |
PROPAGATION_MANDATORY | 使用當前的事務,如果當前沒有事務,就拋出異常; |
PROPAGATION_REQUIRES_NEW | 新建事務,如果當前存在事務,把當前事務掛起; |
PROPAGATION_NOT_SUPPORTED | 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起; |
PROPAGATION_NEVER | 以非事務方式執行,如果當前存在事務,則拋出異常; |
PEOPAGATION_NESTED | 如果當前存在事務,則在潛逃事務內執行。如果當前沒有事務,則執行PROPAGATION_REQUIRED列斯的操作; |
二、Spring中解決事務問題
在Spring中解決事務問題有兩種:聲明式事務和編程式事務(不建議使用)
Spring中支持事務的最底層接口是PlatformTransactionManager
,而我們使用的只能它的子類
public interface PlatformTransactionManager { //獲取事務狀態 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException; //提交 void commit(TransactionStatus var1) throws TransactionException; //回滾 void rollback(TransactionStatus var1) throws TransactionException; }
這個接口中主要用了TransactionDefinition和TransactionStatus兩個類。有興趣的可以看一下。下邊這是它的子類圖,我們這裡使用的是DataSourceTransactionManager作為事務管理類,不管使用何種方式,PlatformTransactionManager這個接口的子類一定要有。
1. 聲明式事務
-
使用註解
配置文件如下:
<!--引入公共的配置文件--> <import resource="application-context.xml"/> <!--Spring提供的事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource"/> </bean> <!-- 開啟事務註解 這裡有個小技巧,如果你的事務管理bean名不是transactionManager 就要給這個標籤配置transaction-manager來指定 --> <tx:annotation-driven/> <!-- 配置spring掃描註解注入service類--> <context:component-scan base-package="cn.lyn4ever.service"/>
然後在類或方法上加上這個@Transactional註解就可以
@Transactional public void insertOne(){ Store store =new Store(); store.setTitle("華為P30"); storeMapper.insertOne(store); int j = 10/0;//指定報錯,讓事務回滾 }
- 使用AOP配置
使用aop的話,我們只需要進行配置,可以對我們寫的業務代碼無任何侵入。如果對AOP知識不是很了解,可以參考我之前的AOP系列教程Spring學習筆記,AOP的配置也有多種,這裡就直接使用aop名稱空間了
<import resource="application-context.xml"/> <!--Spring提供的事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource"/> </bean> <tx:advice id="txAdvice"> <tx:attributes> <!-- 對單獨方法配置屬性--> <tx:method name="insert*" rollback-for="java.lang.Exception"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="serviceTrans" expression="execution(* cn.lyn4ever.serviceaop.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceTrans"/> </aop:config> <!-- 配置spring掃描註解注入service類--> <context:component-scan base-package="cn.lyn4ever.serviceaop"/>
- 以上兩種方式的對比:
- 使用註解能更加細粒度地進行控制,因為並不是所有service方法都需要事務。而使用AOP使用的面向切面編程,所以可以大批量的進行控制,而一般都是在service層進入切入的。
- 使用註解的話,配置簡單,AOP的配置稍微複雜點兒。
- 如果是新項目的話,建議從一開始就使用註解式開發。如果是更改之前沒有用過事務(一般成熟的程序員不會這麼干)的項目,或者無法修改源代碼的情況下,建設使用AOP。
- 個人經驗,建議使用註解開發,能靈活的配置每一個方法及類。使用AOP的話,有時候調試起來不太方便,如果你的調試內容跨越了一個service方法,會進入aop通知方法,很麻煩。
2. 編程式事務
顧名思義,就是將事務的操作直接寫在業務代碼中,這樣做最簡單,但是最不建議。有兩種方式,一種就是將PlatformTransactionManager的實例注入到bean中,使用它。另一種就是使用Spring為我們提供的TransactionTemplate。這裡直接使用第二種,這時,我們只需要使用Spring注入注入transactionManager和這兩個類,但是為了不和之前的配置混淆,我直接new這兩個對象了,也就是說,使用編程式事務,只需要這兩個對象就夠了,不需要其他任何有關事務的配置,只需要一個數據源
@Autowired private DataSource dataSource; @Test public void fun() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); //設置數據源,這個數據源的bean是由Spring提供的 transactionManager.setDataSource(dataSource); TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.execute(txStatus -> { Store store = new Store(); store.setTitle("小米11"); storeMapper.insertOne(store); //製造錯誤,讓事務回滾 int i = 10 / 0; return null; }); }
關注微信公眾號「小魚與Java」,回復Spring獲取代碼地址和更多學習資料