需求场景:
Excel数据录入到数据库,由于校验比较多需要异步处理,并且保留错误信息履历维护到数据库。
解决思路:
首先保留错误信息到数据库就有个很麻烦的点,由于导入时有及其复杂的嵌套逻辑可能需要优先插入数据下面的任务才能进行,而且还要求excel每一条全部录入完一次记录所有错误信息,这就需要事物回滚了,而保存错误信息这个动作由于事物的一致性也会被回滚,所以还要控制事物,让其提交一部分回滚一部分。
1.Excel文件异步提交
controller层
@PostMapping("/import")
public R importDevice(@RequestPart MultipartFile file) throws Exception {
// 在进入实现类之前获取MultipartFile的文件流,要做异步处理就不能在线程里获取流
InputStream inputStream = file.getInputStream();
projectService.importDevice(inputStream);
return R.ok();
}
service接口
@Async
void importDevice(InputStream inputStream) throws Exception;
如果直接把MultipartFile交给Service异步处理直接就报NoSuchFileException异常,原因是主线程Controller中的MultipartFile生成实例同时会生成临时文件,MultipartFile的实例交给异步线程Service处理前Controller瞬间执行结束MultipartFile临时文件会被springboot(spring)销毁,所以子线程获取不到InputStream。
解决方案:在主线程中获取MultipartFile的InputStream,这样子线程中InputStream流作为了入参,就不会产生找不到文件的情况。
2.手动回滚
@Override
@Transactional(rollbackFor = Exception.class)
public void importDevice(InputStream inputStream) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
搭配@Transactional使用。事物出现异常又需要try catch 捕获时,事物注解回滚会失效(try catch脱离Spring管理),手动回滚可以在任何情况下回滚包括try catch
3.脱离Spring事物
业务场景:由于一些必要提交的信息需要在回滚的状态下一样提交,就需要脱离事物形成自定义回滚部分。
// 建立可控任务用于在子线程获取返回数据
FutureTask<Long> futureTask = new FutureTask(() -> importLogService.importRecord(importLog, null));
// 控制子线程脱离Spring事物
new Thread(futureTask).start();
importLog.setId((futureTask.get()));
- 建立一个子线程就不会被Spring管理了,把必须提交的任务放在子线程里就不会被回滚
- 需要在子线程里获取返回值可以建立可控任务,在把任务放到子线程去执行就能获取子线程里获取返回值