场景

项目需要 导入一批数据,对数据进行切割,用多线程跑。

问题点

方法上增加@Transactional,对多线程无效,发生异常,子线程不会回滚,即使在子线程中增加@Transactional。
原因:线程不归spring容器管理,也就不指望通知回滚了。

代码

将大数据进行切割

 // apache自带切割api, num是对应想要切几段
ListUtils.partition(list, num);

使用异常标志、发令枪控制各线程回滚

@Transactional(rollbackFor = Exception.class)
public Result<String> dealData(int sheetMergeCount) {
// todo 业务逻辑...
// 切割数据
List<List<Integer>> list = splitList(sheetMergeCount, 10);
// 异常标志,AtomicBoolean保证线程安全
AtomicBoolean isError = new AtomicBoolean(false);
// 发令枪,长度是切割的子集合数量(线程的数量)
CountDownLatch countDownLatch = new CountDownLatch(list.size());
for (List<Integer> integerList : list) {
// 将任务放进线程池
ThreadPoolUtil.exec(() -> {
// 将事务操作放在新的类中,并开启事务
extraService.upload(integerList, isError, countDownLatch);
});
}
try {
// 等所有线程都跑完,判断异常标识,有异常,进行主线程内回滚
countDownLatch.await();
if (isError.get()) {
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return Result.fail("系统异常,稍后重试");
}
return Result.success("success");
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
log.error("dealData发令枪异常", e);
}
return Result.fail("系统异常,稍后重试");
}
@Service
@Slf4j
public class ExtraServiceImpl implements ExtraService {

@Override
@Transactional(rollbackFor = Exception.class)
public void uploadData(List<Integer> integerList, AtomicBoolean isError, CountDownLatch countDownLatch) {
try {
// todo 业务逻辑
} catch (Exception e) {
log.error("ExtraService.uploadData异常", e);
// 发生异常,进行标记
isError.set(true);
} finally {
// 发令枪倒计时
countDownLatch.countDown();
}
try {
// 等所有线程都跑完,判断异常标识,有异常,线程内进行回滚
countDownLatch.await();
if (isError.get()) {
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
} catch (Exception e) {
log.error("ExtraService.uploadData发令枪异常", e);
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}

引用博客

https://blog.csdn.net/gsc1456/article/details/124748943

其他参考

https://blog.csdn.net/u010978399/article/details/117771620

  • 这篇博客也是使用发令枪的方式实现子线程回滚,不同的是,该博客中每个子线程的事务是除了回滚是手动的外,提交也是手动的。
  • 这篇博客声明了两个计数器,一个主线程的,一个子线程的。当主线程的计数器归零,子线程的发令枪才开枪,子线程才会执行事务判断的部分,实现效果和当前代码我认为是没有区别的