当前位置: 首页 > backend >正文

Spring事务失效的全面剖析

文章目录

    • 1. Spring事务基础
      • 1.1 什么是Spring事务
      • 1.2 Spring事务的实现原理
      • 1.3 `@Transactional`注解的主要属性
      • 1.4 使用Spring事务的简单示例
    • 2. Spring事务失效的常见场景及解决方案
      • 2.1 方法不是public的
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.2 自调用问题(同一个类中的方法调用)
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.3 异常被捕获而未被抛出
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.4 抛出的异常类型不正确
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.5 事务传播行为设置不当
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.6 未被Spring管理的类
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.7 数据库引擎不支持事务
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.8 使用了错误的事务管理器
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.9 多线程环境下的事务问题
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
      • 2.10 代理的限制(CGLIB代理和final方法)
        • 问题描述
        • 问题示例
        • 解决方案
        • 技术原理解释
    • 3. 常见问题与解答
      • Q1: 为什么在同一个类中调用`@Transactional`方法会导致事务失效?
      • Q2: Spring事务默认对哪些异常回滚?
      • Q3: 为什么有时候抛出异常后事务没有回滚?
      • Q4: `@Transactional`注解应该放在接口上还是实现类上?
      • Q5: 有没有办法在一个事务中部分回滚?
      • Q6: 如何在事务提交前或回滚后执行某些操作?
      • Q7: 如何在测试中验证事务是否正常工作?
    • 4. 最佳实践
      • 4.1 设计和开发阶段
      • 4.2 实现阶段
      • 4.3 测试阶段
    • 5. 总结

1. Spring事务基础

1.1 什么是Spring事务

事务是指对数据库执行的一系列操作,这些操作要么全部成功执行,要么全部不执行,以保证数据的一致性和完整性。Spring事务管理是Spring框架中一个强大的功能,它提供了一套完整的事务管理机制,使开发人员能够以声明式或编程式的方式管理事务。

Spring事务管理的核心是抽象出了一套事务管理的API,无论使用JDBC、Hibernate还是JPA,Spring都能以一致的方式提供事务支持。

1.2 Spring事务的实现原理

Spring事务的实现原理主要基于AOP(面向切面编程)和代理模式。当我们在一个方法上添加@Transactional注解时,Spring会使用AOP创建一个代理对象,这个代理对象会在目标方法执行前开启事务,在方法执行后提交事务,如果发生异常则回滚事务。

具体流程如下:

  1. Spring容器初始化时,扫描带有@Transactional注解的方法
  2. 通过BeanPostProcessor创建代理对象(默认使用JDK动态代理,如果类没有实现接口则使用CGLIB代理)
  3. 当调用带有@Transactional注解的方法时,会首先执行代理对象的逻辑
  4. 代理对象负责开启事务、执行原方法、提交或回滚事务

1.3 @Transactional注解的主要属性

@Transactional(propagation = Propagation.REQUIRED,          // 事务传播行为isolation = Isolation.DEFAULT,               // 事务隔离级别timeout = -1,                                // 事务超时时间readOnly = false,                            // 是否只读事务rollbackFor = Exception.class,               // 遇到哪些异常回滚noRollbackFor = FileNotFoundException.class  // 遇到哪些异常不回滚
)

1.4 使用Spring事务的简单示例

下面是一个基本的Spring事务使用示例:

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void createUser(User user) {userRepository.save(user);// 如果后续操作抛出异常,该操作会回滚}
}

2. Spring事务失效的常见场景及解决方案

2.1 方法不是public的

问题描述

Spring官方文档明确指出,@Transactional注解只能应用于public方法上。如果将其应用于非public方法,该注解不会生效。

问题示例
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 事务无效,因为方法不是public的@Transactionalprotected void createUser(User user) {userRepository.save(user);throw new RuntimeException("故意抛出异常");}
}
解决方案

确保所有使用@Transactional注解的方法都是public的。

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 正确使用,方法是public的@Transactionalpublic void createUser(User user) {userRepository.save(user);// 如果抛出异常,事务会回滚}
}
技术原理解释

Spring的事务管理是通过AOP实现的,而Spring AOP的默认行为是只拦截public方法。这是因为在代理对象中,非public方法无法被外部调用,所以Spring AOP也就无法为其创建代理。

2.2 自调用问题(同一个类中的方法调用)

问题描述

当一个类中的方法调用同一个类中的另一个事务方法时,事务不会生效。这是因为在这种情况下,调用的是目标对象的方法,而不是代理对象的方法。

问题示例
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 事务生效的方法@Transactionalpublic void createUserWithRoles(User user, List<Role> roles) {// 直接调用同类中的另一个事务方法this.createUser(user);  // 这里的调用不会使用事务代理// 添加角色逻辑for (Role role : roles) {userRepository.saveUserRole(user.getId(), role.getId());}// 如果这里抛出异常,createUser方法不会回滚throw new RuntimeException("故意抛出异常");}@Transactionalpublic void createUser(User user) {userRepository.save(user);}
}

在上面的例子中,如果createUserWithRoles方法抛出异常,通过调用this.createUser(user)创建的用户不会回滚,因为这是一个自调用,没有通过Spring的事务代理。

解决方案

有以下几种解决方案:

  1. 将方法移到其他类中
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate RoleService roleService;@Transactionalpublic void createUserWithRoles(User user, List<Role> roles) {// 调用其他类的方法,事务会正常工作roleService.createUser(user);for (Role role : roles) {userRepository.saveUserRole(user.getId(), role.getId());}throw new RuntimeException("故意抛出异常");}
}@Service
public class RoleService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void createUser(User user) {userRepository.save(user);}
}
  1. 使用自我注入
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;// 自我注入,获取代理对象@Autowiredprivate UserService self;@Transactionalpublic void createUserWithRoles(User user, List<Role> roles) {// 通过代理对象调用方法,事务会正常工作self.createUser(user);for (Role role : roles) {userRepository.saveUserRole(user.getId(), role.getId());}throw new RuntimeException("故意抛出异常");}@Transactionalpublic void createUser(User user) {userRepository.save(user);}
}
  1. 使用AopContext获取代理对象(需要配置exposeProxy = true):
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void createUserWithRoles(User user, List<Role> roles) {// 获取当前代理对象,调用其方法((UserService) AopContext.currentProxy()).createUser(user);for (Role role : roles) {userRepository.saveUserRole(user.getId(), role.getId());}throw new RuntimeException("故意抛出异常");}
http://www.xdnf.cn/news/6008.html

相关文章:

  • C++:重载>>和<<(输入和输出运算符)
  • [FA1C4] 博客链接
  • OpenTiny icons——超轻量的CSS图标库,引领图标库新风向
  • Weblogic 反序列化远程命令执行漏洞 CVE-2019-2725 详解
  • Eaton XV-102-BE-35TQRC-10是伊顿(Eaton)公司推出的一款高性能触摸屏人机界面(HMI)
  • Python | Dashboard制作
  • 【报错解决】服务器重启后vscode远程连接失败
  • MySQL推荐书单:从入门到精通
  • 3545. 不同字符数量最多为 K 时的最少删除数
  • 【登录认证】JWT令牌
  • RDD-自定义分区器案例
  • 3541. 找到频率最高的元音和辅音
  • mysql8创建用户并赋权
  • Cascadeur2025如何无限制导出FBX文件
  • 优艾智合机器人助力半导体智造,领跑国产化替代浪潮
  • 20250513 空间无限大奇点问题
  • 汽车功能安全--TC3xx MBIST设计要点
  • 分子动力学模拟揭示点突变对 hCFTR NBD1结构域热稳定性的影响
  • 关于vue 本地代理
  • 基于javaweb的SpringBoot爱游旅行平台设计和实现(源码+文档+部署讲解)
  • 日常学习开发记录-rate评价组件
  • AI工具分享篇 | recraft.ai + figma 复刻技术路线图
  • Node.js事件循环中的FIFO原则
  • Docker入门教程:常用命令与基础概念
  • ‌C# 集成 FastDFS 完整指南‌
  • Django 中时区的理解
  • 科学养生,开启健康生活
  • 对抗帕金森:在疾病阴影下,如何重掌生活主动权?
  • PyTorch中的nn.Embedding应用详解
  • 电脑声音小怎么调大 查看声音调整方法