1 事務
1.1 事務簡介與 mysql 中的事務使用
事務這個詞在學習 MySQL 和多線程并發(fā)編程的時候,想必大家或多或少接觸過。那么什么是事務呢?
事務是指一組操作作為一個不可分割的執(zhí)行單元,要么全部成功執(zhí)行,要么全部失敗回滾。在數據庫中,事務可以保證數據的一致性、完整性和穩(wěn)定性,同時避免了數據的異常和不一致情況。常見的事務包括插入、更新、刪除等數據庫操作。事務的核心要素是ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
比如,常見的轉賬操作,以小明給小紅轉賬100元為例,分為如下兩個操作:
- 小明的賬戶 -100元;
- 小紅的賬戶 +100元。
如果沒有事務,第一步操作執(zhí)行成功,而第二步執(zhí)行失敗,就會導致小明賬戶平白無故的扣款而小紅賬戶沒有收到款項的問題。因此,事務的存在是必要的,這一組操作要么全部執(zhí)行成功,要么一起失敗~
在 MySQL 中,事務有三個重要的操作,分別為:開啟事務、提交事務、回滾事務,對應的操作命令如下:
-- 開啟事務
start transaction;
-- 業(yè)務執(zhí)行
...
-- 提交事務
commit;
-- 回滾事務
rollback;
1.2 Spring 編程式事務(手動操作)
與 MySQL 操作事務類似,Spring 手動操作事務也需要三個重要的操作:開啟事務(獲取事務)、提交事務、回滾事務。
SpringBoot 內置了兩個對象:
-
DataSourceTransactionManager
?來獲取事務(開啟事務)、提交或回滾事務的; -
TransactionDefinition
是事務的屬性,在獲取事務的時候需要將TransactionDefinition
傳遞進去從?獲得?個事務TransactionStatus
;
實現代碼如下:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 事務管理器
@Autowired
private DataSourceTransactionManager transactionManager;
// 定義事務屬性
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public int add(UserInfo userInfo) {
// 非空校驗
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 1. 開始事務
TransactionStatus transactionStatus =
transactionManager.getTransaction(transactionDefinition);
int result = userService.add(userInfo);
System.out.println("添加: " + result);
// // 2. 回滾事務
// transactionManager.rollback(transactionStatus);
// 3. 提交事務
transactionManager.commit(transactionStatus);
return result;
}
}
從上述代碼可以看出,雖然可以實現事務,但是操作很繁瑣。因此,我們 常常使用另一種更簡單的方式:基于注解的聲明式事務。
1.3 Spring 聲明式事務(自動操作)
相比手動操作事務來說,聲明式事務非常簡單,只需要在需要的方法上添加 @Transactional
注解,無需手動開啟事務和提交事務。
示例代碼如下:
@Transactional // 聲明式事務(自動提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
// 非空校驗
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int result = userService.add(userInfo);
return result;
}
對于 @Transactional
的幾點說明:
- 該注解可以加在方法或者類上,若加在類上,則說明該類的所有公共方法可以自動的開啟和提交事務 ,無論修飾方法還是類,都只對 public 方法有效;
- 在方法執(zhí)行前自動開啟事務,在方法執(zhí)行完畢(沒有發(fā)生任何異常)自動提交事務。如果 在方法執(zhí)行期間出現異常,會自動回滾事務。
附:@Transactional
的常見參數:
參數 | 說明 |
---|---|
propagation | 定義了事務方法被嵌套調用時,事務如何傳播到被調用的方法。常見取值包括: |
- REQUIRED (默認):如果當前存在事務,則加入該事務;如果當前沒有事務,則創(chuàng)建一個新的事務。 |
|
- REQUIRES_NEW :每次調用方法時都會創(chuàng)建一個新的事務,如果存在當前事務,則將其掛起。 |
|
- SUPPORTS :如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執(zhí)行。 |
|
- NOT_SUPPORTED :以非事務方式執(zhí)行操作,如果當前存在事務,則將其掛起。 |
|
- MANDATORY :如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。 |
|
- NEVER :以非事務方式執(zhí)行操作,如果當前存在事務,則拋出異常。 |
|
- NESTED :如果當前存在事務,則在嵌套事務內執(zhí)行;如果當前沒有事務,則創(chuàng)建一個新的事務。 |
|
isolation | 定義了事務并發(fā)執(zhí)行時,事務之間的隔離程度。常見取值包括: |
- DEFAULT (默認):使用數據庫默認的隔離級別。 |
|
- READ_UNCOMMITTED :最低的隔離級別,事務可以讀取未提交的數據。 |
|
- READ_COMMITTED :事務只能讀取已提交的數據。 |
|
- REPEATABLE_READ :事務在整個過程中保持一致的讀取視圖,防止臟讀和不可重復讀。 |
|
- SERIALIZABLE :最高的隔離級別,事務串行執(zhí)行,避免臟讀、不可重復讀和幻讀。 |
|
timeout | 定義了事務執(zhí)行的最長時間,單位為秒。默認值為-1,表示沒有超時限制。 |
readOnly | 如果設置為true ,表示事務只讀,不會修改數據庫的數據。默認值為false 。 |
rollbackFor | 觸發(fā)事務回滾的異常類數組。當方法拋出指定的異常時,事務將回滾。 |
noRollbackFor | 不觸發(fā)事務回滾的異常類數組。當方法拋出指定的異常時,事務將不會回滾。 |
rollbackForClassName | 觸發(fā)事務回滾的異常類名數組。當方法拋出指定的異常時,事務將回滾。 |
noRollbackForClassName | 不觸發(fā)事務回滾的異常類名數組。當方法拋出指定的異常時,事務將不會回滾。 |
value | 用于指定事務管理器的名稱。如果應用程序中存在多個事務管理器,可以使用該參數指定要使用的事務管理器的名稱。默認情況下,事務將使用默認的事務管理器。 |
transactionManager | 用于指定事務管理器的引用??梢灾苯訉⒁粋€事務管理器對象傳遞給該參數,以指定要使用的事務管理器。默認情況下,事務將使用默認的事務管理器。 |
對于上述表格中的事務隔離級別需要重點掌握,具體后面詳細說。
需要特別注意的是,如果方法中的異常被 try-catch
異常捕獲處理后,則不會再進行事務的回滾。
當然,我們可以通過 throw
將異常拋出,使得事務能夠正常自動回滾。但是這樣子做,try-catch
還有意義嗎?
因此,對于這種情況,更偏向于使用另一種優(yōu)雅的方式,進行手動回滾事務來解決~
如何在聲明式事務中進行手動回滾事務?
使用代碼進行手動回滾事務:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
示例代碼如下:
1.4 @Transactional 的工作原理
- 當調用被@Transactional注解標記的方法時,事務管理器會檢查當前是否存在一個事務。如果存在事務,則該方法將在該事務的上下文中執(zhí)行;如果不存在事務,則會創(chuàng)建一個新的事務。
- 在方法執(zhí)行期間,如果發(fā)生了受檢查異常(checked exception),事務管理器會捕獲該異常,并根據配置的回滾規(guī)則決定是否回滾事務。如果異常被捕獲并且需要回滾事務,則事務將被回滾,方法的執(zhí)行將終止,并將異常傳播給調用方。
- 如果方法成功執(zhí)行并且沒有拋出受檢查異常,事務管理器將提交事務,將數據庫中的更改持久化。
- 如果方法執(zhí)行期間拋出了未受檢查異常(unchecked exception)或錯誤(Error),事務管理器會將事務標記為回滾,并將異常傳播給調用方。
- 如果方法執(zhí)行期間沒有拋出異常,但在方法內部調用了其他被@Transactional注解標記的方法,事務管理器將根據事務的傳播行為決定如何處理這些方法。例如,如果傳播行為設置為REQUIRED,則內部方法將加入當前事務;如果傳播行為設置為REQUIRES_NEW,則內部方法將創(chuàng)建一個新的事務。
具體來看,@Transactional 是基于 AOP 實現的,AOP ?是使?動態(tài)代理實現的。如果?標對象實現了接?,默認情況下會采? JDK 的動態(tài)代理,如果?標對象沒有實現了接?,會使? CGLIB 動態(tài)代理。@Transactional 在開始執(zhí)?業(yè)務之前,通過代理先開啟事務,在執(zhí)?成功之后再提交事務。如果中途遇到的異常,則回滾事務。實現細節(jié)的執(zhí)行流程如圖所示:
2 事務的隔離級別
2.1 事務的四大特性及事務的隔離級別回顧
事務有4 ?特性(ACID),原?性、?致性、隔離性和持久性:
- 原?性: ?個事務中的所有操作,要么全部完成,要么全部不完成。若事務在執(zhí)?過程中發(fā)?錯誤,會被回滾到事務開始前的狀態(tài)。
- ?致性: 在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。即寫?的資料必須完全符合所有的預設規(guī)則,這包含資料的精確度、串聯(lián)性以及后續(xù)數據庫可以?發(fā)性地完成預定的?作。
- 持久性: 事務處理結束后,對數據的修改就是永久的,即便系統(tǒng)故障也不會丟失。
- 隔離性: 數據庫允許多個并發(fā)事務同時對其數據進?讀寫和修改的能?,隔離性可以防?多個事務并發(fā)執(zhí)?時由于交叉執(zhí)??導致數據的不?致。
其中,對于隔離性有隔離級別可以設置。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串?化(Serializable)。
歸根到底,事務隔離級別的設置是為了防止其它事務影響當前事務的一種策略。
在MySQL中,默認是可重復讀(repeatable read)級別。以下是MySQL常見的事務隔離級別以及它們對臟讀、不可重復讀和幻讀問題的解決情況的表格:
事務隔離級別 | 臟讀(Dirty Read) | 不可重復讀(Non-repeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
讀未提交(Read Uncommitted) | 可能發(fā)生 | 可能發(fā)生 | 可能發(fā)生 |
讀已提交(Read Committed) | 避免 | 可能發(fā)生 | 可能發(fā)生 |
可重復讀(Repeatable Read) | 避免 | 避免 | 可能發(fā)生 |
串行化(Serializable) | 避免 | 避免 | 避免 |
對于臟讀、不可重復讀和幻讀的解釋:
-
臟讀(Dirty Read):指一個事務讀取了另一個事務未提交的數據。如果一個事務可以讀取未提交的數據,則會發(fā)生臟讀。
-
不可重復讀(Non-repeatable Read):指在同一個事務中,多次讀取同一行數據時,得到的結果不一致。這是因為在讀取期間,另一個事務修改了該行數據。
-
幻讀(Phantom Read):指在同一個事務中,多次查詢同一個范圍的數據時,得到的結果集不一致。這是因為在查詢期間,另一個事務插入或刪除了符合查詢條件的數據。
每個隔離級別對這些問題的解決情況如下:
-
讀未提交(Read Uncommitted):允許臟讀、不可重復讀和幻讀。一個事務可以讀取另一個事務未提交的數據。
-
讀已提交(Read Committed):避免臟讀。一個事務只能讀取已提交的數據。但是,可能發(fā)生不可重復讀和幻讀,因為在同一個事務中,另一個事務可能會修改數據。
-
可重復讀(Repeatable Read):避免臟讀和不可重復讀。在同一個事務中,多次讀取同一行數據時,得到的結果是一致的。但是,可能發(fā)生幻讀,因為在同一個事務中,另一個事務可能會插入或刪除數據。
-
串行化(Serializable):避免臟讀、不可重復讀和幻讀。事務串行執(zhí)行,保證了數據的一致性和完整性。
但隔離級別的提升會增加并發(fā)性能的開銷,因為更高的隔離級別通常需要使用鎖來實現。
在數據庫中,可以使用如下語句來查詢全局事務隔離級別和當前連接的事務隔離級別:
select @@global.tx_isolation,@@tx_isolation;
2.2 Spring 事務的隔離級別及設置
在Spring框架中,事務的隔離級別可以使用@Transactional
注解來設置。@Transactional
注解可以應用在方法級別或類級別上,用于聲明一個事務性方法或類。
Spring 框架支持以下五個事務隔離級別:
-
DEFAULT
(默認):使用底層數據庫的默認隔離級別。對于大多數數據庫來說,通常是READ_COMMITTED
。 -
READ_UNCOMMITTED
:讀未提交。允許臟讀、不可重復讀和幻讀。這是最低的隔離級別,一個事務可以讀取另一個事務未提交的數據。 -
READ_COMMITTED
:讀已提交。避免臟讀。一個事務只能讀取已提交的數據。但是,可能發(fā)生不可重復讀和幻讀,因為在同一個事務中,另一個事務可能會修改數據。 -
REPEATABLE_READ
:可重復讀。避免臟讀和不可重復讀。在同一個事務中,多次讀取同一行數據時,得到的結果是一致的。但是,可能發(fā)生幻讀,因為在同一個事務中,另一個事務可能會插入或刪除數據。 -
SERIALIZABLE
:串行化。避免臟讀、不可重復讀和幻讀。事務串行執(zhí)行,保證了數據的一致性和完整性,但是性能太低。
可以在@Transactional
注解上使用isolation
屬性來設置事務的隔離級別。例如:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void myTransactionalMethod() {
// 事務性操作
}
需要注意的是,事務的隔離級別還受數據庫本身支持的隔離級別的限制。如果數據庫不支持某個特定的隔離級別,那么Spring框架將盡力使用最接近的隔離級別。
3 Spring 事務傳播機制
3.1 初探事務的傳播機制
事務的傳播機制是用來定義事務在傳播過程中的行為模式的一種機制。 Spring 事務傳播機制定義了多個包含了事務的方法,相互調用時,事務是如何在這些方法進行傳遞的。
對比事務的隔離級別來看,如果說事務的隔離級別是保證多個并發(fā)事務執(zhí)行的可控性的(穩(wěn)定性),則 事務的傳播機制就是保證一個事務在多個調用方法間的可控性的(穩(wěn)定性)。
事務的隔離級別解決的是多個并發(fā)事務調用數據庫的問題:
事務的傳播機制解決的是一個事務在多個節(jié)點(方法)中傳遞的問題:
比如,方法 A 正常執(zhí)行,完成了事務。但是,方法 B 發(fā)生了錯誤。那么,方法 A 進行的事務操作是否要回滾呢?這就是事務的傳播機制需要解決的問題~
3.2 Spring 事務傳播機制的分類及設置
在Spring框架中,事務傳播機制用于定義在多個事務性方法相互調用時,事務如何傳播和交互的規(guī)則。Spring框架提供了七種不同的事務傳播行為:
-
REQUIRED
(需要有):如果當前存在事務,則加入該事務;如果當前沒有事務,則創(chuàng)建一個新的事務。這是最常用的傳播行為。 -
SUPPORTS
(可以有):如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執(zhí)行。 -
MANDATORY
(強制有):如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。 -
REQUIRES_NEW
:創(chuàng)建一個新的事務,并掛起當前事務(如果存在)。新創(chuàng)建的事務與當前事務完全獨立。 -
NOT_SUPPORTED
:以非事務方式執(zhí)行,并且掛起當前事務(如果存在)。 -
NEVER
:以非事務方式執(zhí)行,如果當前存在事務,則拋出異常。 -
NESTED
:如果當前存在事務,則在嵌套事務中執(zhí)行。嵌套事務是獨立于當前事務的子事務,它可以獨立地進行提交或回滾。如果當前沒有事務,則創(chuàng)建一個新的事務。
這些事務傳播行為可以通過@Transactional
注解的propagation
屬性進行設置。例如:
@Transactional(propagation = Propagation.REQUIRED)
public void myTransactionalMethod() {
// 事務性操作
}
需要注意的是,事務傳播行為僅在方法之間的調用時才會生效,對于同一個方法內部的事務性操作,傳播行為不會起作用。
如果將事務比作房子,以伴侶為例子理解(以下圖片來自網絡):
3.3 嵌套事務(NESTED)和加入事務(REQUIRED )的區(qū)別
- 整個事務如果全部執(zhí)行成功,二者的結果是一樣的。
- 如果事務執(zhí)行到一半失敗了,那么加入事務整個事務會全部回滾;而嵌套事務會局部回滾,不會影響上一個方法中執(zhí)行的結果。
寫在最后
?本文被 JavaEE編程之路 收錄點擊訂閱專欄 , 持續(xù)更新中。
?以上便是本文的全部內容啦!創(chuàng)作不易,如果你有任何問題,歡迎私信,感謝您的支持!文章來源:http://www.zghlxwxcb.cn/news/detail-635688.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-635688.html
到了這里,關于【Spring Boot】事務的隔離級別與事務的傳播特性詳解:如何在 Spring 中使用事務?不同隔離級別的區(qū)別?的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!