NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 设计模式

  • 开发规范

  • 经验分享

  • 记录

    • 「记录」SpringBoot与前端传递的json中属性映射读写注解
    • Maven常用命令
    • 「记录」Java使用CAS更新对象字段值
    • 「MyBatis」MyBatis常用标签
    • 改造ruoyi-cloud
    • Mybatis-plus使用JSON类型
    • RuoYi-Cloud-Plus

    • Spring

      • Spring经验总结
      • 「Spring Boot」配置优先级
      • 注解 - @Transactional 事务
        • 属性
          • isolation 隔离级别
          • propagation 事务传播行为
          • readOnly 是否只读
        • 触发回滚的方式
        • 失效场景
          • 1. 注解在非 public 修饰的方法上
          • 2. 注解属性 propagation 设置错误
          • 3. 注解属性 rollbackFor 设置错误
          • 4. 同一个类中方法调用⭐
          • 5. 异常被捕获
          • 6. 数据库引擎不支持事务
        • 常见问题
    • 源码分析

  • 快速开始

  • 笔记

  • 面试题

  • 微服务

  • 踩过的坑

  • Java
  • 记录
  • Spring
NipGeihou
2023-04-18
目录

注解 - @Transactional 事务

# 属性

# isolation 隔离级别

Isolation.DEFAULT

  • DEFAULT :默认值,表示使用底层数据库的默认隔离级别。大部分数据库为 READ_COMMITTED(MySQL 默认隔离级别为 REPEATABLE))

# propagation 事务传播行为

Propagation.REQUIRED

  • REQUIRED :默认值,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
  • NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。

# readOnly 是否只读

true 时只读,不能更新、删除等写操作

# 触发回滚的方式

  1. 抛异常(注意 rollbackFor 的 Exception 级别)
  2. 调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 方法

# 失效场景

参考:一口气说出 6 种,@Transactional 注解的失效场景 - 掘金 (opens new window)

# 1. 注解在非 public 修饰的方法上

笔记

在实践中发现导致事务失效的似乎并不是修饰符,而是 4. 同一个类中方法调用 导致的。

# 2. 注解属性 propagation 设置错误

出现概率低,默认的 propagation 值 REQUIRED 不会出现这个问题,如果要设定其他值时应当清楚明白其作用。

# 3. 注解属性 rollbackFor 设置错误

这是一个比较常见的失效场景,但如果有安装 Alibaba Java Coding Guidelines 插件会提示 方法【XXXXX】需要在Transactional注解指定rollbackFor或者在方法中显式的rollback。 。

# 4. 同一个类中方法调用⭐

这是一个很难发现的场景,在笔者尝试测试非 public 修饰符对事务的影响时发现了这个问题。

@Service
public class TestService{
    
    public void publicTest() {
		this.publicRollback();
	}
    
	@Transactional(rollbackFor = Exception.class)
	public void publicRollback() {
		this.save(......);
		Assert.isTrue(false, "测试public");
	}
    
	public void privateTest() {
		this.privateRollback();
	}
    
	@Transactional(rollbackFor = Exception.class)
	private void privateRollback() {
		this.save(......);
		Assert.isTrue(false, "测试private");
	}
    
	public void protectedTest() {
		this.protectedRollback();
	}
    
	@Transactional(rollbackFor = Exception.class)
	protected void protectedRollback() {
		this.save(......);
		Assert.isTrue(false, "测试protected");
	}
    
}

由于 Controller 无法直接访问 Service 的 private、protected 修饰的方法,因此使用了一个 public 方法间接调用,经测试发现即便是 publicTest 也出现了事务失效的问题。

提示

上述写法 IDEA 会在 this.xxxxRollback() 处警告: @Transactional self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime

@Transactional 自调用 (实际上,目标对象中的方法调用目标对象的另一个方法) 在运行时不会产生实际的事务

解决办法:在 Service 的入口方法上添加 @Transactional 注解

# 5. 异常被捕获

代码中 try...catch 了异常,导致无法触发回滚。

解决办法:在 catch 中抛出一个 RuntimeException 异常

# 6. 数据库引擎不支持事务

略

# 常见问题

1. 控制台打印了检测但不回滚

遇到异常检测不回滚,原因:默认 RuntimeException 级别才回滚,如果是 Eexception 级别的异常需要手动添加

@Transactional(rollbackFor=Exception.class)

捕捉异常后事物不生效,原因:捕捉处理了异常导致框架无法感知异常,自然就无法回滚了。建议:若非实际业务要求,则在业务层统一抛出异常,然后在控制层统一处理

@Transactional(rollbackFor=Exception.class)
public void test() {
     try {
          //业务代码
     } catch (Exception e) {
         // TODO: handle exception
     }   //主动捕捉异常导致框架无法捕获,从而导致事物失效
 }

2. 如何在多线程中使用事务回滚?

需求:多线程执行,任意一个线程抛出异常,所有线程事务回滚。

@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchPutOnShelf(List<Integer> idList) {
    idList.parallelStream().forEach(this::putOnShelf);
    return Boolean.TRUE;
}

相比于直接使用线程池,使用 parallelStream 方法有以下优缺点:

优点:

  • 代码简单,易于理解和使用;
  • 无需手动管理线程池,避免因线程池设置不当导致的性能问题;
  • 对于数据规模较小或处理逻辑较简单的情况,可以自动利用系统的多核资源,提高处理效率。

缺点:

  • 对于数据规模较大或处理逻辑较复杂的情况,可能无法最优地利用系统资源,导致性能下降;
  • 无法手动调整并行度,可能会导致过多的线程竞争和资源占用;
  • 无法控制线程池的参数,如线程数、队列大小等,可能会影响系统的稳定性和可靠性。

3. 事务方法修饰符对事务回滚的影响

参考:Spring Framewor 文档 - 1.4.6. Using @Transactional (opens new window)- Method visibility and @Transactional

应该只对具有 public 可见性的方法应用 @Transactional 注解。如果注解在 protected 、 private 的或包可见的方法,虽然不会报错,但被注解的方法不会表现出配置的事务性设置。如果你需要注解非 public 的方法,请考虑下一段中关于基于类的代理的提示,或者考虑使用 AspectJ 编译时或加载时织入(后面描述)。

当在 @Configuration 类中使用 @EnableTransactionManagement 时,通过注册一个自定义的 transactionAttributeSource Bean,也可以使基于类的代理的 protected 或包可见的方法成为事务性的,就像下面的例子一样。然而,请注意,基于接口的代理中的事务性方法必须始终是 public 的,并定义在被代理的接口中。

笔记

在测试 protected 、 private 修饰符对事务的影响时发现,

参考资料

  • SpringBoot 事务隔离等级和传播行为 - zincredible - 博客园 (opens new window)
  • Transaction Management (opens new window)
  • 一口气说出 6 种,@Transactional 注解的失效场景 - 掘金 (opens new window)
上次更新: 2023/11/13, 12:24:50
「Spring Boot」配置优先级
RuoYi-Vue-Plus

← 「Spring Boot」配置优先级 RuoYi-Vue-Plus→

最近更新
01
Docker Swarm
04-18
02
安全隧道 - gost
04-17
03
Solana最佳实践
04-16
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式