springboot的异步调用@Async和事务@Transactional注解的注意事项

场景描述:我司最近要做一个上传Word转图片,实现预览Word的功能,整体流程为用户前端上传word文件,上传成功之后即提示成功,Java后台异步调用转图片服务。

伪代码:

controller层:接收上传文件

serviceImpl层:1.上传文件->2.上传成功->3.异步调用转图片服务

以前只知道springboot中异步调用使用@Async注解,并且被注解标注的方法必须是public。于是就写出了下面的代码

// serviceImpl中部分方法
 @override
 public String Upload(File file){

  // 1.上传
   Boolean result = upload(file);
   if(result){
      return "上传失败";
   }
  // 2.异步调用转换图片服务
  this.transformToPictures()
  // 3.直接返回上传结果
  return "上传成功"; 
}

@Async
public Re transformToPictures(){
  // 转换图片的逻辑
  return Re.ok();
}

写完之后开始进行单元测试,问题就暴露了出来。当调用上传图片出现异常的时候,会直接影响主方法,从而提示前端,上传出错。其实已经上传成功了。

根据出现的问题可知,其实上面所谓的异步方法并没用异步执行,而是同步执行的。为何加了@Async没有生效的?查了一些资料,

1.Async返回的结果只有void和Fucure<V>l类型,所以上面的代码返回Re显然是不行的。

2.Async注解是基于动态代理实现的,具体什么个原理我没细查,如果你的异步方法是在当前类中书写,并且在当前类使用则必须获取当前类的代理,于是获取当前类,代码改成下面这样。如果是当前类调用其他类的异步方法,则直接调用即可,无需获取代理类。

// serviceImpl中部分方法
 @override
 public String Upload(File file){

  // 1.上传
   Boolean result = upload(file);
   if(result){
      return "上传失败";
   }
  UploadServiceImpl serviceImpl = SpringContextUtil.getBean(UploadServiceImpl.class)
  // 2.异步调用转换图片服务
  serviceImpl.transformToPictures()
  // 3.直接返回上传结果
  return "上传成功"; 
}

// 返回值修改为void
@Async
public void transformToPictures(){

}

但是在springContextUtil.getBean()这里直接报错,说,找不到注入的bean,自己有点晕。暂且放置一边,将代码放到controller层运行,springContextUtil.getBean(MyController.class),结果发现,在controller层是可以异步调用的。那上面的问题怎么回事?想了一会,serviceImpl是service接口的实现类,所以这里的getBean应该放的是Service.class,这样就获得当前接口实现类的代理类,但是此时调用异步方法是没有的,所以要将异步方法加入到service接口中,然后在serviceImpl中重写,再加入@Async注解即可。

3.代码最终改为这下面这样

public class UploadServiceImpl implements UploadService{

// serviceImpl中部分方法
 @Override
 public String Upload(File file){

  // 1.上传
   Boolean result = upload(file);
   if(result){
      return "上传失败";
   }
  UploadService service = SpringContextUtil.getBean(UploadService.class)
  // 2.异步调用转换图片服务
  service.transformToPictures()
  // 3.直接返回上传结果
  return "上传成功"; 
}

// 返回值修改为void
@Async
@Override
public void transformToPictures(){

 }
}

这样,上传成功之后就会立刻返回成功结果,即使异步方法中抛出异常,也不会影响主方法的执行,前端用户也是感知不到的

Tips:

  获取当前类的代理对象除了上述的getBean()方法,还可以通过另一种方法获得,如下:

在springboot项目的pom.xml文件中加入aop依赖

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>

在application.yml中开启aop

aop:
    auto: true
    proxy-target-class: true

在上述代码中获得当前代理类地方通过Java的aop使用

UploadService uploadService = (UploadService) AopContext.currentProxy();

@Transactional注解也有类似的要注意的地方

当本类方法中没有Transactional注解的A方法中调用带有@Transactionalh注解的方法B,这是即使B中抛出异常,B方法也不会被回滚,要想生效,则需要获得本类的代理类,再去调用,才可以。但是如果不是本类,其他类调用,则可以正常回滚。

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注解开启一个新的事务
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值