-
深度剖析 Spring Boot3 中事务失效的场景与解决方案
- 网站名称:深度剖析 Spring Boot3 中事务失效的场景与解决方案
- 网站分类:技术文章
- 收录时间:2025-08-03 01:42
- 网站地址:
“深度剖析 Spring Boot3 中事务失效的场景与解决方案” 网站介绍
在当今的互联网大厂后端开发领域,Spring Boot3 框架凭借其便捷性和强大功能,成为众多项目的首选。其中,事务管理作为保障数据一致性和完整性的关键机制,在开发过程中起着举足轻重的作用。然而,许多开发者在使用 Spring Boot3 的事务功能时,常常会遭遇事务失效的困扰,这不仅影响了应用的稳定性,还可能导致严重的数据问题。本文将深入探讨 Spring Boot3 中事务失效的常见场景,并详细阐述相应的解决方法,帮助广大互联网大厂后端开发人员更好地驾驭 Spring Boot3 的事务管理功能。
事务方法使用非 public 修饰符
在 Spring AOP 的机制里,默认情况下仅支持对公共方法进行事务管理。倘若我们在非公共(例如私有、受保护)方法上使用@Transactional注解,事务将不会生效。这是因为 Spring 的 AOP 代理机制仅会拦截公共方法,对于非公共方法则不会进行代理操作。
假设我们有如下代码:
@Service
public class UserService {
@Transactional
private void saveUser() {
// 数据库操作,保存用户信息
}
}
在上述代码中,saveUser方法被声明为private,即便添加了@Transactional注解,事务也不会生效。要解决这个问题,只需将方法的访问修饰符改为public:
@Service
public class UserService {
@Transactional
public void saveUser() {
// 数据库操作,保存用户信息
}
}
同一类中方法自调用
当一个类中的事务方法调用该类中的另一个事务方法时,外部调用的方法无法触发事务代理,致使内部方法的事务设置失效。这是由于 Spring 的事务管理依赖于代理机制,只有外部对代理对象的调用才能触发事务管理逻辑。
例如,在一个银行转账的业务场景中,代码如下:
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
deduct(fromAccount, amount);
deposit(toAccount, amount);
}
@Transactional
public void deduct(String accountNo, BigDecimal amount) {
Account account = accountRepository.findByAccountNo(accountNo);
if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}
accountRepository.updateBalance(accountNo, account.getBalance().subtract(amount));
}
@Transactional
public void deposit(String accountNo, BigDecimal amount) {
Account account = accountRepository.findByAccountNo(accountNo);
accountRepository.updateBalance(accountNo, account.getBalance().add(amount));
}
}
在上述代码中,transfer方法调用了deduct和deposit方法,但由于它们在同一个类中,内部调用不会经过代理,所以deduct和deposit方法上的@Transactional注解不会生效。解决办法有两种:
将方法拆分到不同 Bean 中,例如:
@Service
public class BankTransferService {
@Autowired
private AccountDeductService accountDeductService;
@Autowired
private AccountDepositService accountDepositService;
@Transactional
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
accountDeductService.deduct(fromAccount, amount);
accountDepositService.deposit(toAccount, amount);
}
}
@Service
public class AccountDeductService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void deduct(String accountNo, BigDecimal amount) {
Account account = accountRepository.findByAccountNo(accountNo);
if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}
accountRepository.updateBalance(accountNo, account.getBalance().subtract(amount));
}
}
@Service
public class AccountDepositService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void deposit(String accountNo, BigDecimal amount) {
Account account = accountRepository.findByAccountNo(accountNo);
accountRepository.updateBalance(accountNo, account.getBalance().add(amount));
}
}
使用 Spring 的AopContext,但使用前需在配置类添加@EnableAspectJAutoProxy(exposeProxy = true)开启代理暴露:
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
BankService proxy = (BankService) AopContext.currentProxy();
proxy.deduct(fromAccount, amount);
proxy.deposit(toAccount, amount);
}
@Transactional
public void deduct(String accountNo, BigDecimal amount) {
Account account = accountRepository.findByAccountNo(accountNo);
if (account.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}
accountRepository.updateBalance(accountNo, account.getBalance().subtract(amount));
}
@Transactional
public void deposit(String accountNo, BigDecimal amount) {
Account account = accountRepository.findByAccountNo(accountNo);
accountRepository.updateBalance(accountNo, account.getBalance().add(amount));
}
}
在配置类中添加:
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
}
异常处理问题
捕获异常却未重新抛出
在事务方法中,如果捕获了异常却未重新抛出,Spring 事务管理器将无法感知异常的发生,从而导致事务不会回滚。例如:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void placeOrder(Order order) {
try {
orderRepository.save(order);
int i = 1 / 0; // 模拟异常
} catch (Exception e) {
// 捕获了异常,事务不会回滚
System.out.println("发生异常:" + e.getMessage());
}
}
}
为确保事务能够正确回滚,应在捕获异常后重新抛出异常,或者将捕获的异常转换为RuntimeException抛出:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void placeOrder(Order order) {
try {
orderRepository.save(order);
int i = 1 / 0; // 模拟异常
} catch (Exception e) {
// 抛出RuntimeException,事务会回滚
throw new RuntimeException("发生异常", e);
}
}
}
异常类型不匹配
默认情况下,Spring 事务仅在遇到未检查异常(即继承自RuntimeException的异常)时才会回滚,而对于检查异常(如IOException等),事务不会自动回滚。例如:
@Service
public class FileUploadService {
@Autowired
private FileRepository fileRepository;
@Transactional
public void uploadFile(File file) {
try {
// 假设这里有文件上传逻辑,可能抛出IOException
fileRepository.save(file);
} catch (IOException e) {
// 捕获IOException,事务不会回滚
System.out.println("文件上传失败:" + e.getMessage());
}
}
}
如果希望在检查异常发生时也能回滚事务,可以在@Transactional注解中明确指定需要回滚的异常类型,如@Transactional(rollbackFor = Exception.class):
@Service
public class FileUploadService {
@Autowired
private FileRepository fileRepository;
@Transactional(rollbackFor = Exception.class)
public void uploadFile(File file) {
try {
// 假设这里有文件上传逻辑,可能抛出IOException
fileRepository.save(file);
} catch (IOException e) {
System.out.println("文件上传失败:" + e.getMessage());
throw e;
}
}
}
事务传播特性设置问题
事务传播特性定义了一个事务方法被调用时,如何与调用者的事务进行交互。如果设置不当,事务可能无法按预期工作。例如,使用PROPAGATION_SUPPORTS(如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行)或PROPAGATION_NOT_SUPPORTED(以非事务方式执行操作,如果当前存在事务,则挂起该事务)时,可能会出现事务未创建或被挂起的情况。
假设我们有如下代码:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(propagation = Propagation.SUPPORTS)
public void saveUser(User user) {
userRepository.save(user);
}
}
在上述代码中,saveUser方法的事务传播特性为PROPAGATION_SUPPORTS,如果调用saveUser方法时没有外部事务,该方法将以非事务方式执行。若业务需求是无论是否有外部事务,都要在事务中执行saveUser方法,应将事务传播特性改为PROPAGATION_REQUIRED(默认值,当前存在事务则加入,不存在则创建新事务):
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(propagation = Propagation.REQUIRED)
public void saveUser(User user) {
userRepository.save(user);
}
}
事务管理器配置错误
在一些复杂的项目中,可能会配置多个事务管理器。如果在使用@Transactional注解时,没有明确指定要使用的事务管理器,可能会导致事务管理器选择错误,进而使事务失效。
例如,在一个同时操作主数据源和从数据源的项目中,配置了两个事务管理器:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public PlatformTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
在业务代码中,如果不指定事务管理器:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}
此时,Spring 可能无法正确选择事务管理器,导致事务失效。解决办法是在@Transactional注解中明确指定事务管理器:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional(transactionManager = "primaryTransactionManager")
public void saveUser(User user) {
userRepository.save(user);
}
}
使用了异步方法
当在异步方法上使用@Transactional注解时,事务可能不会按预期工作。这是因为异步方法通常在新的线程中执行,而事务上下文默认是线程绑定的,新线程无法继承父线程的事务上下文。
例如:
@Service
public class TaskService {
@Async
@Transactional
public void asyncTask() {
// 假设这里有数据库操作
Task task = new Task();
taskRepository.save(task);
}
}
在上述代码中,asyncTask方法是异步方法,其事务配置可能不会生效。解决办法是避免在异步方法上直接使用@Transactional注解,或者使用编程式事务管理在异步方法中手动管理事务。例如,通过获取事务管理器手动开启和提交事务:
@Service
public class TaskService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TaskRepository taskRepository;
@Async
public void asyncTask() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
Task task = new Task();
taskRepository.save(task);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
}
数据库不支持事务
某些数据库操作或数据库类型不支持事务,比如 MySQL 的 MyISAM 引擎就不支持事务,只有 InnoDB 引擎支持事务。如果使用了不支持事务的数据库引擎,即便正确配置了事务,事务也无法生效。
要解决这个问题,需要将数据库引擎修改为支持事务的引擎。例如,在 MySQL 中,将表的引擎从 MyISAM 改为 InnoDB:
ALTER TABLE your_table_name ENGINE = InnoDB;
在互联网大厂后端开发中,Spring Boot3 的事务管理至关重要。了解并掌握事务失效的各种场景及解决方法,能帮助我们在开发过程中有效避免数据不一致等问题,提升应用的稳定性和可靠性。希望本文能为广大后端开发人员在处理 Spring Boot3 事务相关问题时提供有力的参考。
更多相关网站
- 10个SQL优化技巧,性能提升300%(sql优化从哪几方面入手)
- 面试官问你 MySQL 的线上执行 DDL 该怎么做?...
- MySQL 8.0 的隐藏索引:索引管理的利器,还是性能陷阱?
- MySQL实战:Json字段类型详解(mysql中json类型)
- Spring事务失效的12种解决方案!15年踩坑经验浓缩成这份避雷指南
- 面试官:select语句和update语句分别是怎么执行的?
- 详细了解 InnoDB 内存结构及其原理
- java 使用Jdbc连接mysql数据库以及其存在的问题
- 百万订单背后的架构生死局:SpringCloud Alibaba拯救我们的微服务
- 面试官:20 亿手机号存储选 int 还是 string?varchar 还是 char?
- 面试官:MySQL的自增ID用完了,怎么办?
- 别再用雪花算法生成ID了!试试这个吧
- # mysql 中文乱码问题分析(#mysql5.0中文乱码)
- MySQL分页到了后面越来越慢,有什么好的解决办法?
- Spring Boot3 中实现树表结构数据查询及返回全解析
- SQL外连接优化:经过验证的性能提升
- zPaaS低代码平台使用介绍:第一个功能开发
- 面试官:你对索引了解多少,展开说说
- 最近发表
-
- 基于jeecgboot框架的cloud商城源码分享,兼容单体和微服务模式
- 值得学习的15 个优秀开源的 Spring Boot 学习项目
- 基于SpringBoot低代码平台(基于spring boot)
- 火山引擎基于 Zeppelin 的 Flink/Spark 云原生实践
- 十款优质企业级Java微服务开源项目(开源框架,公司项目等)
- 可以直接拿来做项目的开源框架(可以直接拿来做项目的开源框架是什么)
- Github 2024-05-10 Java开源项目日报 Top10
- 推荐这款企业级!物联网平台支持NB-IoT、蜂窝网络等多种接入方式
- 开源的面向中小企业的进销存管理系统
- Jeecgboot3.2版-postgres脚本制作
- 标签列表
-
- mydisktest_v298 (35)
- sql 日期比较 (33)
- document.appendchild (35)
- 头像打包下载 (35)
- 二调符号库 (23)
- acmecadconverter_8.52绿色版 (25)
- 梦幻诛仙表情包 (36)
- 魔兽模型 (23)
- java面试宝典2019pdf (26)
- disk++ (30)
- 加密与解密第四版pdf (29)
- iteye (26)
- centos7.4下载 (32)
- intouch2014r2sp1永久授权 (33)
- usb2.0-serial驱动下载 (24)
- jdk1.8.0_191下载 (27)
- axure9注册码 (30)
- virtualdrivemaster (26)
- 数据结构c语言版严蔚敏pdf (25)
- 兔兔工程量计算软件下载 (27)
- 代码整洁之道 pdf (26)
- ccproxy破解版 (31)
- aida64模板 (28)
- engine=innodb (33)
- shiro jwt (28)