Spring @Async/@Transactional 失效的原因及解决方案

本来上一篇说要写关于AOP的实践的,但是这周刚好遇到了使用@Transactional和@Async的不生效的问题,这篇就进行一个回顾和记录。

之前提到实现AOP的方法有动态代理、编译期,类加载期织入等等,Spring实现AOP的方法则就是利用了动态代理机制,正因如此,才会导致某些情况下@Async和@Transactional不生效。

我们已@Aysnc为例,在Spring boot中使用Async很简单:

@EnableAsync //添加此注解开启异步调用
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

当某些任务执行时间较长,且客户端不需要及时获取结果(如调用第三方API),只要在需要异步调用的任务上添加 @Async即可,如:

@Async
void asyncTask(String keyword) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        //logger
        //error tracking
    }
    System.out.println(keyword);
}

这样asyncTask就会异步执行。 然而,如果在同一个Class内,出现下面这样的情况,先调用一个非异步任务:

private void noAsyncTask(String keyword){
    asyncTask(keyword); //该方法内再调用异步方法
}

@Async
void asyncTask(String keyword) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        //logger
        //error tracking
    }
    System.out.println(keyword);
}

此时,@Async是没有生效的。 原因就是@Async和@Transaction利用了动态代理机制。

当Spring发现@Transactional或者@Async时,会自动生成一个ProxyObject,如:


img_faff7aa745fa0c4753fd1ec9869fcda1.png
ProxyObject

此时调用Class.transactionTask会调用ProxyClass.产生事务操作。
然而当Class里的一个非事务方法调用了事务方法,ProxyClass是这样的:


img_17d3b23e81bc2c16fca0ce5ffdbecee9.png
ProxyObject with noTransactionTask

到这里应该可以看明白了,如果调用了noTransactionTask方法,最终会调用到Class.transactionTask,而这个方法是不带有任何Transactional的信息的,也就是@Transactional根本没有生效哦。
简单来说就是: 同一个类内这样调用的话,只有第一次调用了动态代理生成的ProxyClass,之后一直用的是不带任何切面信息的方法本身。

知道了原因,处理方法也特别简单,就是让noTransactionTask里依旧调用ProxyClass的transactionTask方法:只需要显示利用Spring暴露的AopContext即可。代码如下:

private void noAsyncTask(String keyword){
    // 注意这里 调用了代理类的方法
    ((YourClass) AopContext.currentProxy()).asyncTask(keyword);
}

@Async
void asyncTask(String keyword) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        //logger
        //error tracking
    }
    System.out.println(keyword);
}

记得要在Class上加上@EnableAspectJAutoProxy(exposeProxy = true)来暴露AOP的Proxy对象才行,否则会报错。

或者就可以把这样的方法放到另外一个类里,不要产生类里一个非异步/非事务方法,调用了异步/事务方法,不过大家协同开发同一个文件的话,谁能保证没有人这样调用呢?总而言之无论什么方案,都是使得调用ProxyObject的方法。

如果有本篇任何错误,或者更好的解决方案,欢迎评论私信,我会一一回复。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring框架中,使用@Async注解可以将一个方法标记为异步执行,该方法会在一个新的线程中执行。而@Transactional注解用于开启事务,用于保证数据的一致性。 当一个被@Async注解的方法调用一个被@Transactional注解的方法时,会出现事务失效的情况。这是因为@Transactional注解只能在当前线程中开启事务,而异步方法是在新的线程中执行的,与当前线程不在同一个线程中,因此无法获取当前线程中的事务上下文。 为了解决这个问题,可以使用Spring提供的异步事务处理机制。具体来说,可以在@Async注解的方法上添加@Transaction注解,并设置propagation属性为REQUIRES_NEW,表示开启一个新的事务。这样,在异步方法中执行的数据库操作就可以在新的事务中进行,不会影响到当前线程中的事务。 举个例子: ``` @Service public class UserService { @Autowired private UserRepository userRepository; @Async @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser(User user) { userRepository.save(user); } } ``` 在上述例子中,updateUser方法使用了@Async和@Transactional注解,@Async注解表示该方法是异步执行的,@Transactional注解中设置propagation属性为REQUIRES_NEW,表示开启一个新的事务。 总之,为了避免@Async修饰的方法@Transactional事务失效,需要在异步方法中使用@Transaction注解开启一个新的事务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值