⚡️ 在Spring环境下,如果使用了 @Transactional 注解,那么当你的 inert 操作时异步的话,则会不在当前事务里面,那么后续的回滚操作,不会将这次异步操作的插入进行回滚,那么我们有方式来保证多线程异步场景下的事务吗?
@Service
public class TransactionAsyncService {
@Autowired
private PersonService personService;
@Transactional
void transactionAsync() {
new Thread(() -> {
personService.insertPerson("Cocowwy-1");
}).start();
personService.insertPerson("Cocowwy-2");
throw new RuntimeException("手动回滚");
}
}
😶 在上面的Demo中,是开了一个新的线程对数据库插入了数据 Cocowwy-1 这一条数据,之后再在事务里面插入了数据 Cocowwy-2 之后再手动抛了一个异常执行了回滚的操作。
有经验的开发🐒应该知道,这几行代买应该只会回滚 Cocowwy-2 这行代码,因为异步了,由于ThreadLocal的特性,导致事务是不会传递到异步线程里面的。
那么有什么办法可以做到保证子线程的一致性吗??
⚡️灵机一动⚡️,可以利用 JUC的循环栅栏CyclicBarrier,来手动控制多线程事务的统一提交,手动事务提交可以参考这篇文章:戳我
大致思路是这样,对多线程均开启一个事务,并用循环栅栏挡住线程,当确定所有线程均能够正常插入数据之后,再手动提交事务,如果存在插入失败的线程,则全部线程进行手动回滚。
实现代码大致如下:
/**
* 实现在事务内进行多线程inert操作,并保证事务
* @author cocowwy.cn
* @create 2022-05-05-11:45
*/
@Service
public class TransactionAsyncService {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private PersonService personService;
@Transactional
void transactionAsync() {
CyclicBarrier cb = new CyclicBarrier(10);
AtomicReference<Boolean> rollback = new AtomicReference<>(false);
for (int i = 0; i < 10; i++) {
int currentNum = i;
new Thread(() -> {
// 手动开启事务
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
try {
// insert操作,如果插入数据<1则抛异常
if (personService.insertPerson("Cocowwy-" + currentNum) < 1) {
throw new RuntimeException("插入数据失败");
}
// 等待所有线程的事务结果
cb.await();
// 如果标志需要回滚,则回滚
if (rollback.get()) {
dataSourceTransactionManager.rollback(transaction);
return;
}
dataSourceTransactionManager.commit(transaction);
} catch (Exception e) {
// 如果当前线程执行异常,则设置回滚标志
rollback.set(true);
dataSourceTransactionManager.rollback(transaction);
throw new RuntimeException(e);
}
}).start();
}
}
}