如何使用Spring boot的@Transactional進(jìn)行事務(wù)管理
在 Spring Boot 中,@Transactional
是用于聲明式事務(wù)管理的關(guān)鍵注解。它基于 Spring 的 AOP(面向切面編程)實(shí)現(xiàn),可以簡(jiǎn)化數(shù)據(jù)庫(kù)事務(wù)的管理。
一、前置條件
- 依賴(lài)引入:確保項(xiàng)目中包含
spring-boot-starter-data-jpa
或spring-boot-starter-jdbc
- 啟用事務(wù)管理:Spring Boot 默認(rèn)自動(dòng)配置事務(wù)管理器(如
DataSourceTransactionManager
),無(wú)需手動(dòng)啟用。
二、基本用法
1. 在方法上添加注解
@Service public class UserService { @Autowired private UserRepository userRepository; @Transactional public void createUser(User user) { userRepository.save(user); // 其他數(shù)據(jù)庫(kù)操作 } }
2. 在類(lèi)上添加注解
@Service @Transactional public class UserService { // 類(lèi)中所有 public 方法都會(huì)應(yīng)用事務(wù) }
三、核心配置參數(shù)
1. 傳播行為(Propagation)
控制事務(wù)的邊界,默認(rèn)為 Propagation.REQUIRED
。
@Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser(User user) { // 始終開(kāi)啟新事務(wù) }
常見(jiàn)選項(xiàng):
REQUIRED
(默認(rèn)):如果當(dāng)前存在事務(wù),則加入;否則新建REQUIRES_NEW
:始終新建事務(wù),掛起當(dāng)前事務(wù)(如有)SUPPORTS
:如果當(dāng)前存在事務(wù),則加入;否則非事務(wù)運(yùn)行NOT_SUPPORTED
:非事務(wù)運(yùn)行,掛起當(dāng)前事務(wù)(如有)MANDATORY
:必須在事務(wù)中調(diào)用,否則拋出異常NEVER
:必須在非事務(wù)中調(diào)用,否則拋出異常
2. 隔離級(jí)別(Isolation)
控制事務(wù)的隔離性,默認(rèn)為 Isolation.DEFAULT
(使用數(shù)據(jù)庫(kù)默認(rèn))。
@Transactional(isolation = Isolation.SERIALIZABLE) public void sensitiveOperation() { // 最高隔離級(jí)別 }
3. 超時(shí)時(shí)間(Timeout)
事務(wù)超時(shí)時(shí)間(秒),默認(rèn)-1(使用數(shù)據(jù)庫(kù)默認(rèn))。
@Transactional(timeout = 30) public void longRunningProcess() { // 超過(guò)30秒將回滾 }
4. 只讀模式(readOnly)
優(yōu)化只讀操作,默認(rèn)為 false
。
@Transactional(readOnly = true) public List<User> findAllUsers() { return userRepository.findAll(); }
5. 回滾規(guī)則(rollbackFor/noRollbackFor)
指定觸發(fā)回滾的異常類(lèi)型:
@Transactional(rollbackFor = CustomException.class) public void process() throws CustomException { // 拋出 CustomException 時(shí)回滾 }
四、關(guān)鍵注意事項(xiàng)
1. 方法可見(jiàn)性
必須為 public
:由于 Spring AOP 的實(shí)現(xiàn)機(jī)制,非 public 方法上的 @Transactional
無(wú)效
2. 自調(diào)用問(wèn)題
同類(lèi)中的方法互相調(diào)用時(shí),事務(wù)不會(huì)生效(繞過(guò)代理對(duì)象)
// 錯(cuò)誤示例 public void methodA() { methodB(); // 事務(wù)不生效 } @Transactional public void methodB() { ... }
3. 異常處理
- 默認(rèn)回滾條件:拋出
RuntimeException
或Error
- 檢查型異常(Checked Exception)默認(rèn)不會(huì)觸發(fā)回滾
// 處理檢查型異常 @Transactional(rollbackFor = IOException.class) public void fileOperation() throws IOException { // ... }
4. 多數(shù)據(jù)源事務(wù)
需配置多個(gè)事務(wù)管理器,并通過(guò) @Transactional(value = "specificTransactionManager")
指定
五、調(diào)試技巧
啟用調(diào)試日志:
logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
檢查代理對(duì)象:
System.out.println(userService.getClass().getName()); // 輸出應(yīng)為代理類(lèi):com.sun.proxy.$ProxyXX 或 ...$$EnhancerBySpringCGLIB$$...
六、最佳實(shí)踐
- 服務(wù)層使用:在 Service 層而非 Controller 層使用事務(wù)
- 保持短事務(wù):避免在事務(wù)中包含遠(yuǎn)程調(diào)用、文件IO等耗時(shí)操作
- 精確回滾規(guī)則:明確指定
rollbackFor
而非依賴(lài)默認(rèn)行為 - 結(jié)合 JPA 使用:
@Transactional public void updateUserEmail(Long userId, String email) { User user = userRepository.findById(userId).orElseThrow(); user.setEmail(email); // 自動(dòng)臟檢查,事務(wù)提交時(shí)更新 }
七、完整示例
@Service public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private InventoryService inventoryService; @Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, rollbackFor = {InsufficientStockException.class, PaymentFailedException.class} ) public void placeOrder(Order order) { inventoryService.reduceStock(order.getItems()); // 可能拋出 InsufficientStockException orderRepository.save(order); processPayment(order); // 可能拋出 PaymentFailedException } private void processPayment(Order order) { // 支付邏輯 } }
八、適用于關(guān)系型數(shù)據(jù)庫(kù)
@Transactional
的使用與數(shù)據(jù)庫(kù)層有直接關(guān)系,但它的事務(wù)管理機(jī)制是基于數(shù)據(jù)庫(kù)事務(wù)的。如果 Service 層方法使用了 Elasticsearch 客戶(hù)端讀寫(xiě) Elasticsearch,那么 @Transactional
的行為會(huì)受到影響。
1.@Transactional
的適用范圍
@Transactional
是 Spring 提供的事務(wù)管理機(jī)制,主要用于管理數(shù)據(jù)庫(kù)事務(wù)。- 它依賴(lài)于底層的事務(wù)管理器(如
DataSourceTransactionManager
),而這些事務(wù)管理器通常是與關(guān)系型數(shù)據(jù)庫(kù)(如 MySQL、PostgreSQL)交互的。
數(shù)據(jù)庫(kù)事務(wù):@Transactional
可以確保數(shù)據(jù)庫(kù)操作的原子性、一致性、隔離性和持久性(ACID)。非數(shù)據(jù)庫(kù)操作:對(duì)于非數(shù)據(jù)庫(kù)操作(如 Elasticsearch、Redis、文件系統(tǒng)等),@Transactional
無(wú)法直接管理其事務(wù)。
2.Elasticsearch 與 @Transactional
的關(guān)系
Elasticsearch 是一個(gè)分布式搜索引擎,它本身不支持傳統(tǒng)意義上的事務(wù)(ACID)。因此,@Transactional
對(duì) Elasticsearch 的操作沒(méi)有直接的事務(wù)管理能力。
(1)場(chǎng)景分析
假設(shè)我們的 Service 方法同時(shí)操作了數(shù)據(jù)庫(kù)和 Elasticsearch:
@Service public class UserService { @Autowired private UserRepository userRepository; // 數(shù)據(jù)庫(kù)操作 @Autowired private ElasticsearchClient elasticsearchClient; // Elasticsearch 操作 @Transactional public void createUser(User user) { // 操作數(shù)據(jù)庫(kù) userRepository.save(user); // 操作 Elasticsearch IndexRequest request = new IndexRequest("users") .id(user.getId().toString()) .source("name", user.getName(), "email", user.getEmail()); elasticsearchClient.index(request, RequestOptions.DEFAULT); } }
(2)可能出現(xiàn)的問(wèn)題
- 數(shù)據(jù)庫(kù)事務(wù)回滾,Elasticsearch 操作不回滾:
- 如果 userRepository.save(user) 成功,但后續(xù) Elasticsearch 操作失敗,數(shù)據(jù)庫(kù)事務(wù)會(huì)回滾(因?yàn)?@Transactional 生效),但 Elasticsearch 的數(shù)據(jù)已經(jīng)寫(xiě)入,無(wú)法回滾。
- 數(shù)據(jù)庫(kù)事務(wù)提交,Elasticsearch 操作失敗:
- 如果數(shù)據(jù)庫(kù)操作成功,但 Elasticsearch 操作失敗,數(shù)據(jù)庫(kù)事務(wù)已經(jīng)提交,而 Elasticsearch 數(shù)據(jù)未更新,導(dǎo)致數(shù)據(jù)不一致。
3.如何解決 Elasticsearch 與數(shù)據(jù)庫(kù)的事務(wù)一致性問(wèn)題
由于 Elasticsearch 不支持事務(wù),因此需要采用其他機(jī)制來(lái)保證數(shù)據(jù)一致性。
(1)手動(dòng)補(bǔ)償機(jī)制 在 Elasticsearch 操作失敗時(shí),手動(dòng)回滾數(shù)據(jù)庫(kù)操作。
示例:
@Transactional public void createUser(User user) { try { userRepository.save(user); // 數(shù)據(jù)庫(kù)操作 IndexRequest request = new IndexRequest("users") .id(user.getId().toString()) .source("name", user.getName(), "email", user.getEmail()); elasticsearchClient.index(request, RequestOptions.DEFAULT); // Elasticsearch 操作 } catch (Exception e) { // Elasticsearch 操作失敗,手動(dòng)回滾數(shù)據(jù)庫(kù) TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw e; } }
(2)消息隊(duì)列(最終一致性)
- 將 Elasticsearch 操作異步化,通過(guò)消息隊(duì)列(如 Kafka、RabbitMQ)實(shí)現(xiàn)最終一致性。
- 示例:
數(shù)據(jù)庫(kù)操作完成后,發(fā)送消息到隊(duì)列。
消費(fèi)者從隊(duì)列中讀取消息并更新 Elasticsearch。
(3)兩階段提交(2PC)
- 使用分布式事務(wù)管理器(如 Seata)實(shí)現(xiàn)數(shù)據(jù)庫(kù)和 Elasticsearch 的分布式事務(wù)。
- 這種方法復(fù)雜度較高,通常不推薦用于 Elasticsearch。
(4)本地消息表
- 在數(shù)據(jù)庫(kù)中創(chuàng)建一張消息表,記錄需要同步到 Elasticsearch 的數(shù)據(jù)。
- 通過(guò)定時(shí)任務(wù)或事件監(jiān)聽(tīng)器將數(shù)據(jù)同步到 Elasticsearch。
總結(jié)
@Transactional
只對(duì)數(shù)據(jù)庫(kù)事務(wù)有效:它無(wú)法管理 Elasticsearch 等非關(guān)系型數(shù)據(jù)存儲(chǔ)的事務(wù)。- Elasticsearch 操作與數(shù)據(jù)庫(kù)事務(wù)的一致性:需要通過(guò)補(bǔ)償機(jī)制、消息隊(duì)列等方式實(shí)現(xiàn)。
- 設(shè)計(jì)建議: 盡量避免在同一個(gè)事務(wù)中混合數(shù)據(jù)庫(kù)和 Elasticsearch 操作。
- 采用異步化或最終一致性方案來(lái)保證數(shù)據(jù)一致性。
如果你有更多具體的業(yè)務(wù)場(chǎng)景,可以進(jìn)一步討論如何設(shè)計(jì)解決方案!
到此這篇關(guān)于使用Spring boot的@Transactional進(jìn)行事務(wù)管理的文章就介紹到這了,更多相關(guān)Spring boot @Transactional事務(wù)管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java判斷今天,昨天,前天,不能用秒間隔的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇java判斷今天,昨天,前天,不能用秒間隔的簡(jiǎn)單實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03MyBatis使用注解開(kāi)發(fā)實(shí)現(xiàn)步驟解析
這篇文章主要介紹了MyBatis使用注解開(kāi)發(fā)實(shí)現(xiàn)步驟解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08java JSON解析庫(kù)Alibaba Fastjson用法詳解
這篇文章主要介紹了java JSON解析庫(kù)Alibaba Fastjson用法,結(jié)合實(shí)例形式詳細(xì)分析了java JSON解析庫(kù)Alibaba Fastjson的基本功能、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04