Java事務(wù)失效八大場(chǎng)景詳細(xì)解析
前言
在 Java 開(kāi)發(fā)中,事務(wù)管理是保證數(shù)據(jù)一致性的核心機(jī)制,尤其是在 Spring 框架中,@Transactional注解的使用極大簡(jiǎn)化了事務(wù)配置。然而,在實(shí)際開(kāi)發(fā)中,事務(wù)常常會(huì)因?yàn)橐恍┘?xì)節(jié)問(wèn)題而失效,導(dǎo)致數(shù)據(jù)異常。本文將詳細(xì)解析 Java 事務(wù)失效的八大場(chǎng)景,每個(gè)場(chǎng)景都提供代碼示例與對(duì)應(yīng)的修復(fù)方案。
一、事務(wù)方法非 public 修飾
失效原理
Spring 的@Transactional注解默認(rèn)只對(duì)public方法生效。這是因?yàn)?Spring AOP 在實(shí)現(xiàn)事務(wù)管理時(shí),無(wú)論是 JDK 動(dòng)態(tài)代理還是 CGLIB 代理,都無(wú)法對(duì)非 public 方法(private、protected、默認(rèn)訪問(wèn)權(quán)限)進(jìn)行有效的事務(wù)增強(qiáng)。
失效代碼
java運(yùn)行
@Service
public class UserService {
// 非public方法,事務(wù)注解失效
@Transactional
void updateUser(Long id) {
userMapper.updateStatus(id, 1);
}
}
修復(fù)方案
將事務(wù)方法修改為public訪問(wèn)權(quán)限。
修復(fù)后代碼
java運(yùn)行
@Service
public class UserService {
// 修改為public方法,事務(wù)生效
@Transactional
public void updateUser(Long id) {
userMapper.updateStatus(id, 1);
}
}
二、異常被捕獲且未重新拋出
失效原理
Spring 事務(wù)默認(rèn)僅在遇到未捕獲的RuntimeException或Error時(shí)觸發(fā)回滾。如果方法內(nèi)部使用try-catch捕獲了異常且未重新拋出,事務(wù)管理器會(huì)認(rèn)為沒(méi)有異常發(fā)生,從而不會(huì)執(zhí)行回滾操作。
失效代碼
java運(yùn)行
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
// 模擬異常
int i = 1 / 0;
} catch (Exception e) {
// 捕獲異常但未拋出,事務(wù)不會(huì)回滾
log.error("創(chuàng)建訂單失敗", e);
}
}
}
修復(fù)方案
方案一:捕獲異常后重新拋出
方案二:使用TransactionAspectSupport手動(dòng)觸發(fā)回滾
修復(fù)后代碼(方案一)
java運(yùn)行
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
// 模擬異常
int i = 1 / 0;
} catch (Exception e) {
log.error("創(chuàng)建訂單失敗", e);
// 重新拋出異常,觸發(fā)事務(wù)回滾
throw new RuntimeException("創(chuàng)建訂單失敗", e);
}
}
}
修復(fù)后代碼(方案二)
java運(yùn)行
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
// 模擬異常
int i = 1 / 0;
} catch (Exception e) {
log.error("創(chuàng)建訂單失敗", e);
// 手動(dòng)觸發(fā)事務(wù)回滾
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
三、錯(cuò)誤配置 rollbackFor 屬性
失效原理
@Transactional的rollbackFor屬性用于指定需要回滾的異常類型,默認(rèn)值為{RuntimeException.class, Error.class}。如果業(yè)務(wù)中拋出的是受檢查異常(如IOException、SQLException),且未在rollbackFor中聲明,事務(wù)不會(huì)回滾。
失效代碼
java運(yùn)行
@Service
public class FileService {
// 未指定rollbackFor,受檢查異常不會(huì)觸發(fā)回滾
@Transactional
public void importData(String filePath) throws IOException {
// 讀取文件(可能拋出IOException)
FileReader reader = new FileReader(filePath);
// 數(shù)據(jù)入庫(kù)操作
dataMapper.batchInsert(parseData(reader));
}
}
修復(fù)方案
顯式指定rollbackFor屬性,包含需要回滾的異常類型。
修復(fù)后代碼
java運(yùn)行
@Service
public class FileService {
// 顯式指定rollbackFor包含IOException
@Transactional(rollbackFor = {IOException.class, RuntimeException.class})
public void importData(String filePath) throws IOException {
FileReader reader = new FileReader(filePath);
dataMapper.batchInsert(parseData(reader));
}
}
// 更通用的方式:捕獲所有Exception
@Service
public class FileService {
@Transactional(rollbackFor = Exception.class)
public void importData(String filePath) throws IOException {
// 業(yè)務(wù)邏輯不變
}
}
四、事務(wù)傳播機(jī)制配置不當(dāng)
失效原理
Spring 事務(wù)的傳播機(jī)制決定了事務(wù)方法之間的嵌套行為。若傳播機(jī)制配置不合理(如使用NOT_SUPPORTED、SUPPORTS等),可能導(dǎo)致操作不在事務(wù)中執(zhí)行。
失效代碼
java運(yùn)行
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
// 調(diào)用支付服務(wù)(非事務(wù)方式執(zhí)行)
paymentService.processPayment(order.getId(), order.getAmount());
}
}
@Service
public class PaymentService {
// 配置為非事務(wù)方式執(zhí)行
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void processPayment(Long orderId, BigDecimal amount) {
paymentMapper.insert(new Payment(orderId, amount));
// 若此處發(fā)生異常,不會(huì)回滾
}
}
修復(fù)方案
根據(jù)業(yè)務(wù)需求選擇合適的傳播機(jī)制,常用的是默認(rèn)的REQUIRED(如果當(dāng)前有事務(wù)則加入,否則創(chuàng)建新事務(wù))。
修復(fù)后代碼
java運(yùn)行
@Service
public class PaymentService {
// 使用默認(rèn)傳播機(jī)制REQUIRED
@Transactional
public void processPayment(Long orderId, BigDecimal amount) {
paymentMapper.insert(new Payment(orderId, amount));
}
}
五、同類方法內(nèi)部調(diào)用
失效原理
Spring 事務(wù)基于 AOP 代理實(shí)現(xiàn),事務(wù)增強(qiáng)邏輯在代理對(duì)象中執(zhí)行。若同一個(gè)類中的方法 A 調(diào)用方法 B(B 有@Transactional注解),由于調(diào)用未經(jīng)過(guò)代理對(duì)象,方法 B 的事務(wù)注解會(huì)失效。
失效代碼
java運(yùn)行
@Service
public class UserService {
// 方法A(無(wú)事務(wù))調(diào)用方法B(有事務(wù))
public void updateUserInfo(Long id, String name, Integer age) {
updateUserName(id, name); // 內(nèi)部調(diào)用,事務(wù)失效
updateUserAge(id, age); // 內(nèi)部調(diào)用,事務(wù)失效
}
@Transactional
public void updateUserName(Long id, String name) {
userMapper.updateName(id, name);
}
@Transactional
public void updateUserAge(Long id, Integer age) {
userMapper.updateAge(id, age);
}
}
修復(fù)方案
方案一:將方法拆分到不同的類中
方案二:通過(guò)AopContext獲取代理對(duì)象調(diào)用
修復(fù)后代碼(方案二)
java運(yùn)行
// 1. 首先在啟動(dòng)類開(kāi)啟暴露代理
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true) // 關(guān)鍵配置
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 2. 在Service中通過(guò)代理對(duì)象調(diào)用
@Service
public class UserService {
public void updateUserInfo(Long id, String name, Integer age) {
// 通過(guò)AopContext獲取代理對(duì)象
UserService proxy = (UserService) AopContext.currentProxy();
proxy.updateUserName(id, name); // 代理對(duì)象調(diào)用,事務(wù)生效
proxy.updateUserAge(id, age); // 代理對(duì)象調(diào)用,事務(wù)生效
}
@Transactional
public void updateUserName(Long id, String name) {
userMapper.updateName(id, name);
}
@Transactional
public void updateUserAge(Long id, Integer age) {
userMapper.updateAge(id, age);
}
}
六、數(shù)據(jù)庫(kù)不支持事務(wù)
失效原理
事務(wù)最終依賴數(shù)據(jù)庫(kù)支持。若使用的數(shù)據(jù)庫(kù)引擎不支持事務(wù)(如 MySQL 的MyISAM引擎),即使代碼中配置了事務(wù),也無(wú)法生效。
失效場(chǎng)景
sql
-- 使用MyISAM引擎創(chuàng)建表,不支持事務(wù) CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
此時(shí)即使 Service 層配置了@Transactional,數(shù)據(jù)庫(kù)操作也不會(huì)有事務(wù)保障。
修復(fù)方案
使用支持事務(wù)的數(shù)據(jù)庫(kù)引擎(如 MySQL 的InnoDB)。
修復(fù)后代碼
sql
-- 使用InnoDB引擎創(chuàng)建表,支持事務(wù) CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
七、未被 Spring 容器管理
失效原理
若事務(wù)所在的類未被 Spring 容器掃描并實(shí)例化(如未加@Service、@Component等注解),@Transactional注解會(huì)因沒(méi)有代理對(duì)象而失效。
失效代碼
java運(yùn)行
// 未加@Service注解,未被Spring管理
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Transactional
public void updateStock(Long productId, Integer quantity) {
productMapper.decreaseStock(productId, quantity);
}
}
修復(fù)方案
為類添加 Spring 注解(如@Service),確保其被 Spring 容器管理。
修復(fù)后代碼
java運(yùn)行
// 添加@Service注解,被Spring容器管理
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Transactional
public void updateStock(Long productId, Integer quantity) {
productMapper.decreaseStock(productId, quantity);
}
}
八、多線程場(chǎng)景下的事務(wù)隔離
失效原理
在事務(wù)方法中啟動(dòng)新線程執(zhí)行數(shù)據(jù)庫(kù)操作時(shí),新線程的操作不會(huì)納入當(dāng)前事務(wù)管理(線程間事務(wù)上下文獨(dú)立)。即使主線程事務(wù)回滾,新線程的操作也可能已提交。
失效代碼
java運(yùn)行
@Service
public class BatchService {
@Autowired
private UserMapper userMapper;
@Autowired
private LogMapper logMapper;
@Transactional
public void batchProcess(List<Long> userIds) {
// 主線程操作
userMapper.batchUpdateStatus(userIds, 1);
// 新線程執(zhí)行日志記錄(不在當(dāng)前事務(wù)中)
new Thread(() -> {
logMapper.insert(new Log("批量處理用戶: " + userIds));
}).start();
}
}
修復(fù)方案
避免在事務(wù)方法中使用多線程執(zhí)行數(shù)據(jù)庫(kù)操作,或使用分布式事務(wù)協(xié)調(diào)機(jī)制。
修復(fù)后代碼
java運(yùn)行
@Service
public class BatchService {
@Autowired
private UserMapper userMapper;
@Autowired
private LogMapper logMapper;
@Transactional
public void batchProcess(List<Long> userIds) {
// 主線程操作
userMapper.batchUpdateStatus(userIds, 1);
// 同一事務(wù)中執(zhí)行日志記錄
logMapper.insert(new Log("批量處理用戶: " + userIds));
}
}
總結(jié)
事務(wù)失效的核心原因通常與以下幾點(diǎn)相關(guān):
- 代理機(jī)制限制(非 public 方法、同類內(nèi)部調(diào)用)
- 異常處理不當(dāng)(捕獲未拋出、未配置 rollbackFor)
- 事務(wù)屬性配置錯(cuò)誤(傳播機(jī)制不合理)
- 基礎(chǔ)環(huán)境問(wèn)題(數(shù)據(jù)庫(kù)不支持、未被 Spring 管理)
- 并發(fā)場(chǎng)景下的事務(wù)隔離問(wèn)題
在實(shí)際開(kāi)發(fā)中,需結(jié)合業(yè)務(wù)場(chǎng)景合理配置事務(wù)屬性,同時(shí)注意編碼規(guī)范,避免上述陷阱,才能確保事務(wù)機(jī)制有效運(yùn)行,保障數(shù)據(jù)一致性。
到此這篇關(guān)于Java事務(wù)失效八大場(chǎng)景的文章就介紹到這了,更多相關(guān)Java事務(wù)失效場(chǎng)景內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在mybatis中使用mapper進(jìn)行if條件判斷
這篇文章主要介紹了在mybatis中使用mapper進(jìn)行if條件判斷,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01
Java 添加、更新和移除PDF超鏈接的實(shí)現(xiàn)方法
PDF超鏈接用一個(gè)簡(jiǎn)單的鏈接包含了大量的信息,滿足了人們?cè)诓徽加锰嗫臻g的情況下渲染外部信息的需求。這篇文章主要介紹了Java 添加、更新和移除PDF超鏈接的實(shí)現(xiàn)方法,需要的朋友可以參考下2019-05-05
IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無(wú)法識(shí)別即未被標(biāo)識(shí)的解決辦法
在學(xué)習(xí)SpringMVC課程中,基于IDEA新建maven項(xiàng)目模塊后,webapp目錄未被標(biāo)識(shí),即沒(méi)有小藍(lán)點(diǎn)的圖標(biāo)顯示,所以本文給大家介紹了IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無(wú)法識(shí)別即未被標(biāo)識(shí)的解決辦法,需要的朋友可以參考下2024-03-03
SpringBoot項(xiàng)目中處理返回json的null值(springboot項(xiàng)目為例)
本文以spring boot項(xiàng)目為例給大家介紹SpringBoot項(xiàng)目中處理返回json的null值問(wèn)題,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下2019-10-10
Java代碼審計(jì)的一些基礎(chǔ)知識(shí)你知道嗎
這篇文章主要介紹了基于Java的代碼審計(jì)功能的基礎(chǔ)知識(shí),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-09-09
JAVA maven項(xiàng)目使用釘釘SDK獲取token、用戶
這篇文章主要介紹了JAVA maven項(xiàng)目使用釘釘SDK獲取token、用戶,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

