在整個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獲取代碼地址和更多學習資料

