spring中@Transactional注解和事務(wù)的實(shí)戰(zhàn)
前言
在開發(fā)過程中,遇到多個(gè)數(shù)據(jù)庫(kù)操作的時(shí)候往往只知道加上@transactional注解(spring框架)但是沒有系統(tǒng)學(xué)習(xí)數(shù)據(jù)庫(kù)的事務(wù)知識(shí)以及各種用法,本文會(huì)舉例介紹數(shù)據(jù)庫(kù)中事務(wù)的概念以及用法
一、事務(wù)是什么?
在實(shí)際的項(xiàng)目開發(fā)過程中會(huì)涉及很多不可分割的數(shù)據(jù)庫(kù)操作,如轉(zhuǎn)賬業(yè)務(wù)的進(jìn)賬和出帳,要么全部執(zhí)行成功,要么全部失敗回滾。這樣一些的對(duì)數(shù)據(jù)庫(kù)操作的集合就叫事務(wù)。現(xiàn)在假設(shè)數(shù)據(jù)庫(kù)中有一個(gè)表accounts
| balance | user_id |
|---|---|
| 1000 | 1 |
| 100 | 2 |
在數(shù)據(jù)庫(kù)中執(zhí)行一段事務(wù)的sql如下:
-- 顯式事務(wù)示例 BEGIN TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; COMMIT; -- 或 ROLLBACK 回滾
很多人會(huì)覺得這里的COMMIT或者ROLLBACK之前,數(shù)據(jù)庫(kù)操作只是在內(nèi)存中,并沒有更改表中的數(shù)據(jù),需要注意的是,這里的COMMIT或者ROLLBACK 之前,數(shù)據(jù)已經(jīng)是持久化了,也就是寫入了磁盤(數(shù)據(jù)庫(kù)表中)。
二、事務(wù)的特性
根據(jù)對(duì)事務(wù)的理解,可以歸納出事務(wù)應(yīng)該具有的特性
原子性(Atomicity):事務(wù)被視為不可分割的最小單元,要么全部提交成功,要么全部失敗回滾。
一致性(Consistency):事務(wù)執(zhí)行前后,數(shù)據(jù)庫(kù)從一個(gè)一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€(gè)一致狀態(tài)。
隔離性(Isolation):多個(gè)事務(wù)并發(fā)執(zhí)行時(shí),一個(gè)事務(wù)的操作不應(yīng)影響其他事務(wù)。
持久性(Durability):事務(wù)提交后,其對(duì)數(shù)據(jù)的修改是永久性的。
2.1隔離性
在上述特性中比較難理解的應(yīng)該是隔離性,可以想象這樣一個(gè)場(chǎng)景:
因?yàn)榫W(wǎng)絡(luò)延遲或者條件異常,事務(wù)A未提交還沒來得及回滾
-- 事務(wù)A BEGIN TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
事務(wù)B開始查詢事務(wù),查詢的結(jié)果就是balance = balance - 100
-- 事務(wù)B BEGIN TRANSACTION; select balance WHERE user_id = 1 from accounts; commit;
事務(wù)A回滾,此刻a,b兩個(gè)事務(wù)間就發(fā)生了干擾,a的事務(wù)的提交影響了b事務(wù)對(duì)數(shù)據(jù)庫(kù)的正常讀取。
-- 事務(wù)A ROLLBACK ;--回滾
所以隔離性就是指的的是a事務(wù)對(duì)與b事務(wù)的互不影響的程度。
2.2事務(wù)的隔離級(jí)別
事務(wù)的隔離級(jí)別也可以叫做事務(wù)的互不影響程度的級(jí)別。
讀未提交(Read Uncommitted):影響程度最高,即使事務(wù)沒有commit,另外的事務(wù)也可以讀到,可能導(dǎo)致臟讀。
-- ---------------------- -- 窗口1(事務(wù)A) -- ---------------------- -- 設(shè)置隔離級(jí)別為讀未提交 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; -- 修改A賬戶余額(未提交) UPDATE account SET balance = 800 WHERE name = 'A賬戶'; SELECT * FROM account; -- 查看修改后結(jié)果:A賬戶800 -- ---------------------- -- 窗口2(事務(wù)B) -- ---------------------- -- 設(shè)置隔離級(jí)別為讀未提交 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; -- 查詢A賬戶余額(讀到未提交的數(shù)據(jù)) SELECT * FROM account; -- 結(jié)果:A賬戶800(臟讀) -- 窗口1(事務(wù)A)回滾 ROLLBACK; -- 窗口2再次查詢 SELECT * FROM account; -- 結(jié)果:A賬戶恢復(fù)1000(臟讀數(shù)據(jù)消失)
讀已提交(Read Committed):只能讀取已提交的數(shù)據(jù),避免臟讀但可能出現(xiàn)不可重復(fù)讀(一個(gè)事務(wù)內(nèi)的多次查詢結(jié)果不一致)。即b開啟事務(wù)
進(jìn)行第一次查詢,此時(shí)a事務(wù)開啟事務(wù),a更改完數(shù)據(jù)后,事務(wù)提交,b開啟第二次查詢,查詢結(jié)果不一致。
-- ---------------------- -- 窗口1(事務(wù)A) -- ---------------------- SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; --設(shè)置讀已提交的隔離級(jí)別 -- 修改A賬戶余額并提交 UPDATE account SET balance = 800 WHERE name = 'A賬戶'; COMMIT; -- 提交事務(wù) -- ---------------------- -- 窗口2(事務(wù)B) -- ---------------------- SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; -- 第一次查詢(事務(wù)A未提交時(shí)) SELECT * FROM account; -- 結(jié)果:A賬戶1000 -- 此時(shí)事務(wù)A已提交... -- 第二次查詢(事務(wù)A已提交) SELECT * FROM account; -- 結(jié)果:A賬戶800(不可重復(fù)讀) ROLLBACK;
可重復(fù)讀(Repeatable Read):確保同一事務(wù)多次讀取結(jié)果一致(相當(dāng)于事務(wù)開始時(shí)候進(jìn)行了一次數(shù)據(jù)庫(kù)快照,事務(wù)內(nèi)的查詢的是查詢開啟事務(wù)時(shí)刻的數(shù)據(jù)庫(kù)快照的數(shù)據(jù)),避免不可重復(fù)讀但可能出現(xiàn)幻讀(同一事務(wù)內(nèi)多次查詢返回不同行數(shù))。
-- ---------------------- -- 窗口1(事務(wù)A) -- ---------------------- SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; -- 修改A賬戶余額并提交 UPDATE account SET balance = 800 WHERE name = 'A賬戶'; COMMIT; -- ---------------------- -- 窗口2(事務(wù)B) -- ---------------------- SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; -- 第一次查詢 SELECT * FROM account; -- 結(jié)果:A賬戶1000 -- 此時(shí)事務(wù)A已提交... -- 第二次查詢(結(jié)果與第一次一致) SELECT * FROM account; -- 結(jié)果:A賬戶1000(可重復(fù)讀) -- 提交事務(wù)B后,才能看到A的修改 COMMIT; SELECT * FROM account; -- 結(jié)果:A賬戶800
這里需要強(qiáng)調(diào)下為什么會(huì)出現(xiàn)幻讀同一事務(wù)內(nèi)多次查詢返回不同行數(shù))。
雖然不可重復(fù)讀讀的是快照讀,但是使用update/insert/delete等時(shí),是會(huì)先當(dāng)前讀,也就是說的是已提交的數(shù)據(jù),此刻的快照讀會(huì)更新為當(dāng)前使用update/insert/delete等時(shí)數(shù)據(jù)庫(kù)的快照,所以再之后的普通讀select中的快照和最開始的快照的版本是不一樣的了,讀的數(shù)據(jù)也就不一樣了。幻讀例子如下:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
-- 插入新記錄
INSERT INTO account(name, balance) VALUES ('C賬戶', 500);
COMMIT;SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; -- 第一次查詢(返回2條記錄) SELECT COUNT(*) FROM account; -- 此時(shí)事務(wù)A插入新記錄并提交... -- 第二次查詢(仍返回2條記錄) SELECT COUNT(*) FROM account; -- 執(zhí)行更新操作后再次查詢(出現(xiàn)幻讀) UPDATE account SET balance = balance+100 WHERE name = 'A賬戶'; SELECT COUNT(*) FROM account; -- 返回3條記錄 COMMIT;
串行化(Serializable):最高隔離級(jí)別,完全禁止并發(fā)問題,所有事務(wù)完全同步性能最低(按照一定的順序執(zhí)行)。事務(wù)A插入數(shù)據(jù),事務(wù)B在查詢時(shí)被阻塞,避免幻讀。
-- 事務(wù)A
-- ----------------------
-- 窗口1(事務(wù)A)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- 查詢當(dāng)前數(shù)據(jù)
SELECT * FROM account; -- 結(jié)果:A賬戶1000,B賬戶1000
-- ----------------------
-- 窗口2(事務(wù)B)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
-- 插入新數(shù)據(jù)(會(huì)被事務(wù)A阻塞,直到A提交或回滾)
INSERT INTO account (name, balance) VALUES ('C賬戶', 1000);
-- 窗口1提交事務(wù)
COMMIT;
-- 窗口2的插入操作繼續(xù)執(zhí)行(此時(shí)事務(wù)A已提交)
-- 提交后,窗口1再次查詢會(huì)看到新數(shù)據(jù)(幻讀解決)
三、@Transactional注解
@Transactional注解簡(jiǎn)介
@Transactional是Spring框架提供的聲明式事務(wù)管理注解,用于簡(jiǎn)化數(shù)據(jù)庫(kù)事務(wù)操作。通過在方法或類上添加該注解,可以自動(dòng)管理事務(wù)的開啟、提交、回滾等操作,無需手動(dòng)編寫事務(wù)代碼。
基本用法
在Spring Boot項(xiàng)目中,需要在啟動(dòng)類或配置類上添加@EnableTransactionManagement以啟用事務(wù)管理功能。隨后可以在服務(wù)層方法上使用@Transactional注解:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
}
常用屬性配置
@Transactional提供多個(gè)屬性用于定制事務(wù)行為:
#使用注解時(shí)候默認(rèn)配置如下
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
timeout = -1,
readOnly = false,
rollbackFor = {RuntimeException.class, Error.class}
)自定義注解配置
@Transactional(
propagation = Propagation.REQUIRED, --事務(wù)傳播行為
isolation = Isolation.DEFAULT, --隔離級(jí)別,數(shù)據(jù)庫(kù)默認(rèn)不可重讀
timeout = 30, --定義超時(shí)時(shí)間,超過自動(dòng)給回滾
readOnly = false, --不只讀,可修改
rollbackFor = {SQLException.class}, --遇到異常捕獲后必須回滾
noRollbackFor = {NullPointerException.class} --遇到NullPointerException異常時(shí)不回滾事務(wù)
)
public void updateUser(User user) {
// 業(yè)務(wù)邏輯
}
事務(wù)傳播行為
Spring定義了7種事務(wù)傳播行為,常用選項(xiàng)包括:
REQUIRED:默認(rèn)值,當(dāng)前有事務(wù)則加入,沒有則新建REQUIRES_NEW:總是新建事務(wù),暫停當(dāng)前事務(wù)NESTED:在當(dāng)前事務(wù)中嵌套子事務(wù)SUPPORTS:有事務(wù)則加入,沒有則以非事務(wù)方式執(zhí)行NOT_SUPPORTED:以非事務(wù)方式執(zhí)行,暫停當(dāng)前事務(wù)MANDATORY:必須在事務(wù)中調(diào)用,否則拋出異常NEVER:不能在事務(wù)中調(diào)用,否則拋出異常
事務(wù)隔離級(jí)別
隔離級(jí)別控制事務(wù)間的可見性:
DEFAULT:使用數(shù)據(jù)庫(kù)默認(rèn)級(jí)別READ_UNCOMMITTED:讀未提交READ_COMMITTED:讀已提交REPEATABLE_READ:可重復(fù)讀SERIALIZABLE:串行化
異常處理與回滾
默認(rèn)只在運(yùn)行時(shí)異常和Error時(shí)回滾,可通過以下屬性調(diào)整:
rollbackFor:指定觸發(fā)回滾的異常類型noRollbackFor:指定不觸發(fā)回滾的異常類型rollbackForClassName:通過類名指定回滾異常noRollbackForClassName:通過類名指定不回滾異常
性能優(yōu)化建議
對(duì)于只讀操作,建議明確設(shè)置readOnly=true:
@Transactional(readOnly = true)
public User getUser(Long id) {
return userRepository.findById(id);
}
避免在事務(wù)方法中進(jìn)行遠(yuǎn)程調(diào)用或耗時(shí)操作,防止事務(wù)長(zhǎng)時(shí)間占用連接。
四、 事務(wù)不生效的可能原因
在很多面試題會(huì)問事務(wù)不生效的原因, @Transactional 是基于Spring AOP實(shí)現(xiàn)的事務(wù)切面,所以失效本質(zhì)上可以歸因于AOP代理未生效或切面邏輯被阻斷。Spring通過 @EnableTransactionManagement 開啟事務(wù)支持,底層使用 TransactionInterceptor 攔截目標(biāo)方法,生成代理對(duì)象(JDK動(dòng)態(tài)代理或CGLIB代理)。只有通過代理對(duì)象調(diào)用方法時(shí),事務(wù)切面才會(huì)生效;若直接調(diào)用目標(biāo)對(duì)象(如 this.方法() ),會(huì)繞過代理,導(dǎo)致事務(wù)失效。
方法訪問權(quán)限非public
Spring事務(wù)代理要求目標(biāo)方法必須是public,若定義為private/protected/default,代理無法攔截方法調(diào)用。
@Transactional // 失效
private void saveData() {
// 操作數(shù)據(jù)庫(kù)
}
自調(diào)用問題
同類中非事務(wù)方法調(diào)用事務(wù)方法,因直接調(diào)用this而非代理對(duì)象,導(dǎo)致攔截失效。
public class OrderService {
public void createOrder() {
this.saveOrder(); // 自調(diào)用,事務(wù)失效
}
@Transactional
public void saveOrder() {
// 保存訂單
}
}
異常被捕獲未拋出
spring默認(rèn)僅對(duì)RuntimeException和Error回滾,若捕獲異常且未重新拋出,事務(wù)管理器無法感知異常。示例:
@Transactional
public void update() {
try {
jdbcTemplate.update("...");
} catch (DataAccessException e) {
// 捕獲后未拋出,事務(wù)不會(huì)回滾
}
}
通過@Transactional注解指定需回滾的異常:
@Transactional(rollbackFor = IOException.class)
將受檢異常轉(zhuǎn)換為事務(wù)管理器可識(shí)別的類型:
catch (IOException e) {
throw new RuntimeException("Wrapped exception", e);
}數(shù)據(jù)庫(kù)引擎不支持事務(wù)
如MySQL的MyISAM引擎不支持事務(wù),需改用InnoDB。配置示例:
CREATE TABLE test (
id INT PRIMARY KEY
) ENGINE=InnoDB; // 必須為InnoDB
未啟用事務(wù)管理
Spring Boot需配置@EnableTransactionManagement(默認(rèn)自動(dòng)開啟),傳統(tǒng)項(xiàng)目遺漏此注解會(huì)導(dǎo)致事務(wù)失效。示例缺失:
@SpringBootApplication
// 若手動(dòng)配置需添加@EnableTransactionManagement
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
特殊場(chǎng)景:final/static方法
代理無法增強(qiáng)final/static方法,導(dǎo)致事務(wù)失效。示例:
@Transactional
public final void process() { // final方法失效
// 業(yè)務(wù)邏輯
}
五、分布式事務(wù)考慮
對(duì)于跨服務(wù)的事務(wù)操作,@Transactional只能管理本地事務(wù)。分布式場(chǎng)景可考慮:
- Seata框架
- 消息隊(duì)列最終一致性
- TCC模式
- SAGA模式
總結(jié)
在開發(fā)過程中遇到過在同一條件下查詢出不同的數(shù)據(jù)結(jié)果,最后發(fā)現(xiàn)是對(duì)事務(wù)的理解使用欠缺導(dǎo)致。這個(gè)問題在開發(fā)過程中通過還挺常見的,不僅僅是在面對(duì)事務(wù)的時(shí)候加個(gè)@Transactional注解就完事了。
到此這篇關(guān)于spring中@Transactional注解和事務(wù)的實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)spring @Transactional內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- spring中12種@Transactional的失效場(chǎng)景(小結(jié))
- Spring中@Transactional用法詳細(xì)介紹
- springboot中事務(wù)管理@Transactional的注意事項(xiàng)與使用場(chǎng)景
- Spring @Transactional工作原理詳解
- Spring @Transactional注解失效解決方案
- spring @Transactional 無效的解決方案
- spring中@Transactional?注解失效的原因及解決辦法
- Spring事務(wù)@Transactional注解四種不生效案例場(chǎng)景分析
- spring的@Transactional注解用法解讀
相關(guān)文章
詳解Spring Security中獲取當(dāng)前登錄用戶的詳細(xì)信息的幾種方法
本文主要介紹了詳解Spring Security中獲取當(dāng)前登錄用戶的詳細(xì)信息的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Java實(shí)現(xiàn)PDF轉(zhuǎn)圖片的三種方法
有些時(shí)候我們需要在項(xiàng)目中展示PDF,所以我們可以將PDF轉(zhuǎn)為圖片,然后已圖片的方式展示,效果很好,Java使用各種技術(shù)將pdf轉(zhuǎn)換成圖片格式,并且內(nèi)容不失幀,本文給大家介紹了三種方法實(shí)現(xiàn)PDF轉(zhuǎn)圖片的案例,需要的朋友可以參考下2023-10-10
如何在spring事務(wù)提交之后進(jìn)行異步操作
這篇文章主要為大家介紹了如何在spring事務(wù)提交之后進(jìn)行異步操作,這些異步操作必須得在該事務(wù)成功提交后才執(zhí)行,回滾則不執(zhí)行,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2023-09-09
Java實(shí)現(xiàn)學(xué)生管理系統(tǒng)(控制臺(tái)版本)
這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)控制臺(tái)版本的學(xué)生管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
Java實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲示例代碼
這篇文章主要為大家介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java游戲開發(fā)有一定幫助,需要的可以參考一下2022-05-05
對(duì)比Java設(shè)計(jì)模式編程中的狀態(tài)模式和策略模式
這篇文章主要介紹了Java設(shè)計(jì)模式編程中的狀態(tài)模式和策略模式對(duì)比,文中列舉了兩種模式的相似點(diǎn)和不同點(diǎn),并都舉了代碼的實(shí)例作為參照,需要的朋友可以參考下2016-04-04
Dubbo異步調(diào)用的實(shí)現(xiàn)介紹
dubbo默認(rèn)使用同步的方式調(diào)用。但在有些特殊的場(chǎng)景下,我們可能希望異步調(diào)用dubbo接口,從而避免不必要的等待時(shí)間,這時(shí)候我們就需要用到異步。那么dubbo的異步是如何實(shí)現(xiàn)的呢?下面就來看看這個(gè)問題2022-09-09
javafx實(shí)現(xiàn)圖片3D翻轉(zhuǎn)效果方法實(shí)例
程序?qū)崿F(xiàn)思路: 在javafx中Node對(duì)象有一個(gè)effect屬性,可以用于實(shí)現(xiàn)各種特效。PerspectiveTransform特效可以使Node對(duì)象實(shí)現(xiàn)透視變換。因此我們可以通過計(jì)算透視變換中每個(gè)點(diǎn)的位置來實(shí)現(xiàn)3D翻轉(zhuǎn)特效。2013-04-04

