645 字
3 分钟
Transactional注解在悲观锁下的失效原因

在学习Redis时,为确保多线程下的线程同步问题,我在工具函数中添加了synchronized锁,并在函数外添加了@Transactional注解,以便发生错误时回滚操作。

以“一人一单”功能代码为例:

但是!这样的写法仍然是线程不安全的! 因为我是在函数内部加的锁,事务是在函数结束后才提交的。这意味着整个函数是先释放锁,再提交事务。而先释放锁,再提交事务,在这两个事件之间存在一段空窗期,其他线程可以在这个空窗期内重新获取锁,这意味着其他线程仍可以创建订单!

于是我们把锁放在函数外部,在调用这个函数前加锁,这样不就能确保我先提交事务,再释放锁了吗:

但是!这样的写法会导致@Transactional失效,函数将缺少事务回滚的功能。

因为@Transactionnal是Spring提供的注解,而Spring的事务是通过AOP动态代理实现的,只有通过代理对象调用方法时,事务才会生效。

我是这样理解AOP动态代理机制的:以事务回滚为例,Spring在运行时会为这个接口实现类创建一个代理对象,在代理对象中,会为添加了@Transactionnal注解的函数的前后动态地插入事务逻辑相关的代码,但实际的代码中是没有事务逻辑相关的代码的。而此时,因为我的createVoucherOrder是定义在接口实现类中的一个(工具)函数,我调用createVoucherOrder时,实际生效的代码为this.createVoucherOrder,调用的是未插入事务逻辑代码前的函数,而不是插入事务逻辑代码后的函数,因此事务不会生效

那么应该如何解决这个问题呢——只需要把这个函数添加到接口中,然后获取代理对象,使用代理对象来调用createVoucherOrder()即可,如果 Spring 用的是 JDK 动态代理,那么定义在接口实现类中的函数,Spring都无法作动态处理(即无法动态地插入对应的功能性代码),只有定义在接口中的函数才能被Spring识别到,进而动态插入对应的功能性代码

默认情况下 Spring 不会暴露代理对象。要想在运行时通过 AopContext.currentProxy() 获取代理,就必须显式启用,要在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)

Transactional注解在悲观锁下的失效原因
https://gunbrad.xyz/posts/transactional注解在悲观锁下的失效原因/
作者
Gunbrad
发布于
2025-09-22
许可协议
CC BY-NC-SA 4.0