今天遇到一个开发场景,导入一个excel(多个sheet),将里面的每行数据入库,入库成功则直接保存,失败则返回各行异常信息,也就是行与行直接互不影响,是独立的事务
大概的结构是这样的,有两个service,一个是ExcelImportService,另一个是MaterialService,调用过程就是ExcelImportService作为入口调用MaterialService,二话不说直接上代码
ExcelImportService.java
/**
* Excel导入
*
* @param excelFile excel文件
* @param params 参数
* @return
*/
public Object excelImport(MultipartFile excelFile, Map<String, Object> params) {
// 返回错误信息 key -> sheet名称 , value -> 对应sheet行错误信息
Map<String, List<String>> result = new HashMap<>(16);
// 导入物料表(这里只是模拟一下数据,假装这个list里面有东西)
List<MaterialViewModel> materials = new ArrayList<>();
// 通过构造注入的MaterialService
this.materialService.batchImport(materials, result);
return result;
}
MaterialService.java
/**
* 批量导入
*
* @param viewModels 导入数据
* @param result 返回异常记录
*/
@Transactional(rollbackFor = Exception.class)
public void batchImport(List<MaterialViewModel> viewModels, Map<String, List<String>> result) {
// 错误信息列表
List<String> errorList = new ArrayList<>();
// 新增物料
if (viewModels != null && !viewModels.isEmpty()) {
// 遍历数据
for (int i = 0; i < viewModels.size(); i++) {
MaterialViewModel temp = viewModels.get(i);
try {
// AopContext提供的方法你可以在任何时候都能获取到代理对象,解决内部调用问题
String errorMessage = ((MaterialService) AopContext.currentProxy()).rowImport(temp);
// 错误信息不为空
if (!errorMessage.isEmpty()) {
errorList.add("第" + (i + 1) + "行," + errorMessage);
}
} catch (Exception e) {
errorList.add("第" + (i + 1) + "行," + e.getMessage());
}
}
}
// 如果行错误信息不为空
if (!errorList.isEmpty()) {
result.put("物料表", errorList);
}
}
/**
* 单条excel行导入
*
* @param viewModel
* @return
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public String rowImport(MaterialViewModel viewModel) {
StringBuilder errorMessage = new StringBuilder();
try {
// 物料编号
if (StringUtils.isEmpty(viewModel.getMaterialCode())) {
errorMessage.append("物料编码不能为空");
}
// 校验通过则添加
if (errorMessage.length() == 0) {
add(viewModel);
}
return errorMessage.toString();
} catch (Exception e) {
// 记录日志
EvLog.error(e.getMessage());
errorMessage.append(e.getMessage());
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return errorMessage.toString();
}
}
启动类上需要加上@EnableAspectJAutoProxy(exposeProxy = true)
返回结果如下
excel中一共有四条数据,有一条数据不正确,所以数据库只插入了三条
总结
当前应用场景的实现主要是通过Spring的事务注解@Transactional的propagation属性的Propagation.REQUIRES_NEW属性值,官方的解释如下
翻译一下就是创建一个新事务,如果存在,则挂起当前事务,再说细致一点就是它会启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行,但是值得注意的是Spring的@Transactional是基于AOP实现的,所以内部方法的调用是无法开启新的事务的,所以如果我们上述代码直接用batchImport()调用rowImport()是不会生成新事务的(具体的底层原因小伙伴可自行查阅资料,反正我是不知道233),但是为了业务的规范的统一,确实要放到一个service里面,所以就用了这一行代码((MaterialService) AopContext.currentProxy()).rowImport(temp),通过这行代码就解决了内部调用失效的问题
以上就是自己的分享,如有理解有误的地方,请小伙伴帮我纠正