SpringBoot异步与事务一起使用的问题怎么解决

最近遇到的一个场景,在一个被 @Transactional 注解的方法A中中调用了一个被 @Async 注解标记的方法B,由于方法B 在执行时方法A 的事务没有提交,但是方法B在执行过程中获取不到方法A中尚未提交的数据,从而最终倒是方法B执行异常。

@Transactional
public void create(User user){
// 如果用户已存在,则先删除
delete(user.id);

// 创建用户
int userId = insert(user);

// 更新用户信息
update(userId);

}
@Async
public void update(Integer userId){
Icon icon = getUserIcon(userId);

// 更新用户图片
updateUserPohot(userId,icon);

}

像上面的代码,我为创建用户的方法上标记了@Transactional事务注解,然后在其中调用了update()更新方法,这个方法上标记了@Async 注解。这样代码虽然看起来没有什么问题,但是实际在执行update()方法时,由于是其他线程去执行的,就会导致有可能 create()方法对应的事务还没有提交,update() 方法就无法读取到新插入的 user 记录,从而导致更新失败。

解决方案

通过调整逻辑保证事务在调用异步方法前被提交

如何解决SpringBoot异步与事务一起使用的问题

这个问题的原因是由于 @Transactional 和 @Async 注解一起使用导致的,那么我们可以从这个方向入手,首先我们可以先确认将create()方法的事务提交后,然后再去执行异步更新方法:

public void create(User user){
int userId = doCreate(user);

// 更新用户信息
update(userId);

}
@Transactional
public void doCreate(User user){
// 如果用户已存在,则先删除
delete(user.id);

// 创建用户
return insert(user);

}
@Async
public void update(Integer userId){
Icon icon = getUserIcon(userId);

// 更新用户图片
updateUserPohot(userId,icon);

}

异步方法放在事务方法外调用,这样异步方法就能够读取到已经提交的事务数据了。

我们可以使用TransactionTemplate替换@Transactional注解

@Autowired
TransactionTemplate transactionTemplate;

public void create(User user){
int userId = transactionTemplate.execute(status->
{
// 如果用户已存在,则先删除
delete(user.id);

// 创建用户
return insert(user);

});

// 更新用户信息
update(userId);

}
@Async
public void update(Integer userId){
Icon icon = getUserIcon(userId);

// 更新用户图片
updateUserPohot(userId,icon);

}

通过使用TransactionTemplate来细分事务粒度,确保在调用异步方法之前已经提交了事务。

上面的方案基本都能 解决问题,下面是从网上找到的,spring 给出的解决方案:

@Transactional
public void create(User user){
// 如果用户已存在,则先删除
delete(user.id);

// 创建用户
int userId = insert(user);

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
// 更新用户信息
update(userId);

}
});

}
@Async
public void update(Integer userId){
Icon icon = getUserIcon(userId);

// 更新用户图片
updateUserPohot(userId,icon);

}

通过将异步方法注册为事务提交后的操作,这样Spring可以自动帮我们在事务提交后执行对应的操作。



异步与事务是在SpringBoot中常用的两个功能,但是当同时使用时,却容易出现一些问题。 那么,我们应该如何解决SpringBoot异步与事务一起使用的问题呢? 本文将分为七个小标题,详细分析这一问题的解决方案。
1、SpringBoot中异步与事务的原理
在SpringBoot中,我们可以使用@Async注解来进行异步操作,使用@Transactional注解来进行事务控制。 但是,当在异步方法中调用事务方法时,可能会出现事务无法回滚的问题,这是由于默认情况下,异步方法和主方法使用的是同一个线程池,导致事务被提交。
2、@Async注解的使用
在使用@Async注解时,需要在主类上添加@EnableAsync注解,这样才能够开启异步功能。 同时,在异步方法上也需要添加@Async注解,表示该方法将以异步方式执行。 例如:
@Async
public void asyncMethod() {
}
3、@Transactional注解的使用
在SpringBoot中,我们可以使用@Transactional注解来进行事务控制。 在添加了该注解后,Spring会自动为该方法加上事务,然后在方法执行完毕后自动提交或回滚。 例如:
@Transactional
public void transactionMethod() {
}
4、异步方法中使用事务方法的问题
当在异步方法中调用事务方法时,事务可能会被提交导致无法回滚。 为了解决这个问题,我们需要创建一个独立的线程池来执行异步方法,从而避免和主方法使用同一个线程池的问题。 代码示例:
@Autowired
private ThreadPoolTaskExecutor myThread;
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncMethod() {
myThread.execute(() -> {
transactionMethod();
});
}
5、@Transactional(propagation=Propagation.REQUIRES_NEW)的使用
在事务方法上添加@Transactional(propagation = Propagation.REQUIRES_NEW)注解,表示将该方法放到一个新的事务中进行处理,从而避免和异步方法使用同一个事务。 例如:
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncMethod() {
transactionMethod();
}
6、ThreadLocal的使用
为了避免异步方法和主方法使用同一个事务,我们可以使用ThreadLocal来管理事务。 我们可以在异步方法中获取主方法中的事务,并将其设置到ThreadLocal中,然后在异步方法中获取该事务进行操作。 代码示例:
@Autowired
private DataSourceTransactionManager transactionManager;
@Async
public void asyncMethod() {
TransactionStatus transactionStatus = transactionManager
.getTransaction(new DefaultTransactionDefinition());
TransactionSynchronizationManager.bindResource(transactionManager.getDataSource(),transactionStatus);
//执行异步方法
transactionManager.commit(transactionStatus);
}
7、总结
在使用SpringBoot异步和事务的过程中,可能会出现事务无法回滚的问题。 为了解决这一问题,我们可以使用独立的线程池、@Transactional(propagation = Propagation.REQUIRES_NEW)和ThreadLocal来管理事务。 这些方法在实际项目中都可以使用,根据具体情况选用,可以避免代码中出现问题,提高代码的健壮性。