B站視頻:https://www.bilibili.com/video/BV1eV411u7cg
技術(shù)文檔:https://d9bp4nr5ye.feishu.cn/wiki/HX50wdHFyiFoLrkfEAAcTBdinvh
一、什么是事務(wù)
簡單來說事務(wù)就是一組對數(shù)據(jù)庫的操作要么都成功,要么都失敗。事務(wù)要保證可靠性,必須具備四個特性:ACID。
- A:原子性:事務(wù)是一個原子操作單元,要么完全執(zhí)行,要么完全不執(zhí)行。事務(wù)中的所有操作要么全部成功,要么全部失敗,沒有中間狀態(tài)。
- C:一致性:事務(wù)在執(zhí)行前和執(zhí)行后都必須保持數(shù)據(jù)庫的一致性狀態(tài)。
- I:隔離性:事務(wù)的隔離性確保并發(fā)執(zhí)行的事務(wù)彼此不會相互干擾。
- D:一致性:一旦事務(wù)提交,其結(jié)果應(yīng)該是永久性的,即使在系統(tǒng)故障的情況下也是如此。
二、什么是聲明事務(wù),什么是編程事務(wù)
聲明事務(wù)
聲明式事務(wù)是通過配置的方式來管理事務(wù)的行為,聲明式事務(wù)的好處是可以將事務(wù)管理與業(yè)務(wù)邏輯相分離,提高了代碼的可讀性和維護性。
編程事務(wù)
編程式事務(wù)是通過編寫代碼顯式地管理事務(wù)的開始、提交和回滾。使用編程式事務(wù)可以更加靈活地控制事務(wù)的細節(jié),但需要更多的代碼來處理事務(wù)管理,可能導致代碼的冗余和增加了復雜性。
三、Spring 如何實現(xiàn)聲明事務(wù)和編程事務(wù)的
聲明事務(wù)
聲明事務(wù)的代碼很簡單,我們也是經(jīng)常使用的。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional
public void performTransaction() {
// 事務(wù)邏輯
}
}
編程事務(wù)
編程事務(wù),需要自己來控制事務(wù)的流程,更加靈活但也更加復雜,一般不建議使用。(實際上我也沒在生產(chǎn)環(huán)境中用過)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class TransactionService {
private final TransactionTemplate transactionTemplate;
@Autowired
public TransactionService(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void performTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 事務(wù)邏輯
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
}
}
四、聲明事務(wù)是怎么實現(xiàn)的
雖然我們實現(xiàn)事務(wù)的方式有聲明式和編程式,但在實際的使用中,我們只會用聲明式,所以我們有必要來深入理解一下聲明事務(wù)。
其實簡單來說在Spring中,我們開啟聲明事務(wù)用的是@Transactional
,本質(zhì)上是使用的 代理和AOP來實現(xiàn)。
- 事務(wù)攔截器鏈(Interceptor Chain):Spring的聲明式事務(wù)依賴于AOP技術(shù),在運行時動態(tài)生成代理對象并創(chuàng)建事務(wù)攔截器鏈。在方法調(diào)用鏈中,每個事務(wù)攔截器都會被依次調(diào)用,并根據(jù)事務(wù)屬性的定義決定是否開啟、提交或回滾事務(wù)。
- 事務(wù)切點(Transaction Pointcut):事務(wù)切點定義了哪些方法需要被事務(wù)攔截器攔截并應(yīng)用事務(wù)邏輯。切點通過表達式語言(如Spring表達式語言)或基于注解的方式來指定匹配的方法。Spring提供了靈活的切點表達式來滿足各種粒度的事務(wù)控制需求。
- 事務(wù)屬性解析:在聲明式事務(wù)中,事務(wù)屬性可以通過注解(如@Transactional)或配置文件來指定。事務(wù)屬性包括隔離級別、傳播行為、超時設(shè)置等。Spring會解析事務(wù)屬性,并將其應(yīng)用于方法上,以確定事務(wù)的行為。事務(wù)屬性解析器根據(jù)事務(wù)定義的優(yōu)先級,從全局配置或方法級別的注解中獲取事務(wù)屬性。
- 事務(wù)管理器(Transaction Manager):事務(wù)管理器是Spring框架的核心組件之一。它負責處理實際的事務(wù)管理操作,與底層的數(shù)據(jù)訪問技術(shù)(如JDBC、Hibernate等)進行交互。事務(wù)管理器負責事務(wù)的創(chuàng)建、提交和回滾,并與當前線程進行綁定。Spring提供了多種事務(wù)管理器的實現(xiàn),如
DataSourceTransactionManager
、JpaTransactionManager
等,可以根據(jù)具體的數(shù)據(jù)訪問技術(shù)進行配置。 - 事務(wù)同步器(Transaction Synchronization):事務(wù)同步器用于在事務(wù)的不同階段注冊回調(diào)方法。在事務(wù)提交或回滾時,事務(wù)同步器會觸發(fā)注冊的回調(diào)方法,以執(zhí)行一些額外的操作。例如,清理數(shù)據(jù)庫連接、提交緩存數(shù)據(jù)等。Spring利用事務(wù)同步器來確保與事務(wù)相關(guān)的資源的正確管理和釋放。
- 事務(wù)切面(Transaction Aspect):事務(wù)切面是由事務(wù)攔截器和事務(wù)切點組成的,它定義了在目標方法執(zhí)行前后應(yīng)用事務(wù)邏輯的規(guī)則。事務(wù)切面通過AOP技術(shù)將事務(wù)管理邏輯與業(yè)務(wù)邏輯進行解耦。當目標方法被調(diào)用時,事務(wù)切面會根據(jù)事務(wù)屬性的定義,決定是否開啟、提交。
五、@Transactional 注解的參數(shù)
在聲明事務(wù)中,我們只需要和注解 @Transactional 打交道,所以我們有必要來深入理解一下這個注解中的參數(shù)配置。
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
// 事務(wù)管理器、暫時先忽略它,我們也不會去修改這個參數(shù)的值
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
// 事務(wù)傳播行為
Propagation propagation() default Propagation.REQUIRED;
// 事務(wù)隔離級別
Isolation isolation() default Isolation.DEFAULT;
// 事務(wù)超時時間 -1,為永久不超時, 單位是秒
int timeout() default -1;
// 事務(wù)超時時間,可以設(shè)置單位,比如 timeoutString = "30s"
String timeoutString() default "";
// 是否只讀事務(wù)
boolean readOnly() default false;
// 對哪些異常進行回滾
Class<? extends Throwable>[] rollbackFor() default {};
// 對哪些異常進行回滾【異常全限定名】
String[] rollbackForClassName() default {};
// 對哪些異常不回滾
Class<? extends Throwable>[] noRollbackFor() default {};
// 對哪些異常不回滾【異常全限定名】
String[] noRollbackForClassName() default {};
}
rollbackFor和rollbackForClassName的區(qū)別,直接來看使用方式。 最好使用rollbackFor 可以在編譯的時候就幫我買檢查是不是對的。
@Transactional(rollbackFor = Exception.class, rollbackForClassName = {"java.lang.Exception"})
@Transactional 注解的參數(shù)雖然多,但絕大部分都很好理解。這里主要是來說兩個重要且不好理解的參數(shù)propagation
和 isolation
propagation (事務(wù)傳播行為)
事務(wù)的傳播行為是指:當前事務(wù)方法被調(diào)用的時候,需要做什么樣的操作。它的配置如下:
值(小寫方便閱讀) | 描述 |
---|---|
REQUIRED(required) 默認值 |
1.如果當前沒有事務(wù),則創(chuàng)建一個新的事務(wù),并將當前方法作為事務(wù)的起點。 2.如果當前已經(jīng)存在事務(wù),則加入到當前事務(wù)中,成為當前事務(wù)的一部分。 3.當前事務(wù)的提交和回滾都將影響到該方法。 |
REQUIRES_NEW (requires_new) | 1.無論當前是否存在事務(wù),都創(chuàng)建一個新的事務(wù)。 2.如果當前存在事務(wù),則將當前事務(wù)掛起,并啟動一個新的事務(wù)。 3. 當前方法獨立于外部事務(wù)運行,它有自己的事務(wù)邊界。 |
SUPPORTS(supports) | 1. 如果當前存在事務(wù),則加入到當前事務(wù)中,成為當前事務(wù)的一部分。 2.如果當前沒有事務(wù),則以非事務(wù)方式執(zhí)行。 3.支持當前事務(wù)的執(zhí)行,但不強制要求存在事務(wù)。 |
NOT_SUPPORTED (not_supported) | 1.以非事務(wù)方式執(zhí)行操作。 2.如果當前存在事務(wù),則將其掛起。 3.該方法在一個沒有事務(wù)的環(huán)境中執(zhí)行。 |
NEVER(never) | 1.以非事務(wù)方式執(zhí)行操作。 2.如果當前存在事務(wù),則拋出異常,表示不允許在事務(wù)中執(zhí)行該方法。 |
MANDATORY(mandatory) | 1.要求當前存在事務(wù),否則拋出異常。 2.該方法必須在一個已經(jīng)存在的事務(wù)中被調(diào)用。 |
NESTED (nested) | 1.如果當前存在事務(wù),則在嵌套事務(wù)中執(zhí)行。 2.如果當前沒有事務(wù),則行為類似于 REQUIRED,創(chuàng)建一個新的事務(wù)。 |
存在事務(wù)的時候REQUIRED和NESTED的區(qū)別:REQUIRED 是加入當前事務(wù),成為當前事務(wù)的一部分,NESTED 是生成嵌套事務(wù),本質(zhì)上是兩個事務(wù)。(具體區(qū)別下面實踐演示)
isolation(事務(wù)隔離級別)
其實就是我們之前學習數(shù)據(jù)庫時候的數(shù)據(jù)庫隔離級別了。
值(小寫方便閱讀) | 描述 |
---|---|
DEFAULT (default) | 默認的,看當前數(shù)據(jù)庫默認的隔離級別是什么。 |
READ_UNCOMMITTED (read_uncommitted) | 讀未提交 |
READ_COMMITTED (read_committed) | 讀已提交 |
REPEATABLE_READ (repeatable_read) | 可重復讀 |
SERIALIZABLE (serializable) | 序列化 |
六、@Transactional 實踐
在使用 @Transactiona 注解的時候,一定要設(shè)置rollbackFor的值,默認情況下是不回滾的檢查類異常,比如 IOException、SQLException 等。
理論
在深入理解事務(wù)的傳播行為之前,我們需要理解三個基本的概念,理解了它們我們就理解了事務(wù)傳播行為。它們分別是:嵌套事務(wù)、新事務(wù)、當前事務(wù)。
為了理解方便,這里我們先約定一下,有兩個事務(wù)方法A和B,在A里面調(diào)用B。且A事務(wù)的定義如下不會改變,B事務(wù)的傳播行為可能會變。
@Transactional(rollbackFor = Exception.class)
public void A() {
userMapper.insertUser("A",1);
sqlTestService.B();
}
public void B() {
userMapper.insertUser("B",2);
}
嵌套事務(wù)
修改B事務(wù)的傳播行為,讓它生成嵌套事務(wù)
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
嵌套事務(wù)和父事務(wù)是有關(guān)聯(lián)的,當A事務(wù)回滾的時候,B事務(wù)一定回滾。
當B事務(wù)異?;貪L的時候,要判斷在A里面是否try了B事務(wù),如果try就A不會回滾,只是B回滾。
新事務(wù)
修改B事務(wù)的傳播行為,讓它生成新事務(wù)
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
既然都說了是新事務(wù),那A、B事務(wù)沒有什么必然的關(guān)系了。
當前事務(wù)
修改B事務(wù)的傳播行為,讓它加入當前事務(wù)
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
既然說是加入當前事務(wù),那其實本質(zhì)上還是一個事務(wù),不管怎么樣的異常,也不管如何處理異常,A、B方法都是一起提交、或一起回滾。
實踐
insertUser 方法就是一個簡單的插入語句,為了避免誤會,這里直接給出來。
@Insert("INSERT INTO t_users (`name`, `age`) VALUES (#{name}, #{age})")
void insertUser(@Param("name") String name,@Param("age") Integer age);
異常的話,是直接手動拋出一個異常
throw new RuntimeException("xxxxx");
B方法是否try:是指在A方法調(diào)用B方法的時候,是否使用了try catch 如下:文章來源:http://www.zghlxwxcb.cn/news/detail-505470.html
public void A() {
userMapper.insertUser("A",1);
try {
sqlTestService.B();
}catch (Exception e) {
e.printStackTrace();
}
}
代碼很簡單,來回變換很多,就不展示了,直接給執(zhí)行結(jié)果:文章來源地址http://www.zghlxwxcb.cn/news/detail-505470.html
B事務(wù)類型 | 異常方法 | B方法是否try | 插入數(shù)據(jù)結(jié)果 |
---|---|---|---|
新事務(wù) | A方法 | 否 | B |
新事務(wù) | A方法 | 是 | B |
新事務(wù) | B方法 | 否 | 空 |
新事務(wù) | B方法 | 是 | A |
嵌套事務(wù) | A方法 | 否 | 空 |
嵌套事務(wù) | A方法 | 是 | 空 |
嵌套事務(wù) | B方法 | 否 | 空 |
嵌套事務(wù) | B方法 | 是 | A |
加入當前事務(wù) | A方法 | 否 | 空 |
加入當前事務(wù) | A方法 | 是 | 空 |
加入當前事務(wù) | B方法 | 否 | 空 |
加入當前事務(wù) | B方法 | 是 | 空 |
到了這里,關(guān)于Spring使用@Transactional 管理事務(wù),Java事務(wù)詳解。的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!