优化EasyExcel导入

2023-10-18 14:17:23 浏览数 (1)

优化EasyExcel导入

EasyExcel 导入可以参考我这篇文章 http://nwjshm.cn/archives/48.html 在真实场景的情况下Excel数据量很庞大,如果需要做一些字典转换,io一次数据库的时间挺大的

优化思路

  1. 使用Mybatis-Plus sql拦截器实现真批量添加
  2. 如果需要io数据库 我建议可以把字典表的数据缓存到redis 或者存储为map 集合数据 减少io数据库次数

实现

  1. 重构EasyExcel监听器
代码语言:javascript复制
@Slf4j
public abstract class ExcelListener<T,R> extends AnalysisEventListener<T> {

    /**
     * 自定义用于暂时存储data
     * 可以通过实例获取该值
     */
    private List<T> datas = new ArrayList<>(2000);


    /**
     * 每解析一行都会回调invoke()方法
     * @param data  读取后的数据对象
     * @param context 内容
     */
    @Override
    public abstract void invoke(T data, AnalysisContext context);

    /**
     * 读取完后操作
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

        //判断集合是否为空
        if (oConvertUtils.listIsEmpty(datas)) {
            log.info("Excel中没有数据");
            throw new ApiException(503, "Excel中没有数据,请检查模版是否正确");
        }
     log.info("所有数据读取完成");
    }

    /**
     * 异常方法 (类型转换异常也会执行此方法)  (读取一行抛出异常也会执行此方法)
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.info("有异常");
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", (excelDataConvertException.getRowIndex() 1),
                    (excelDataConvertException.getColumnIndex()   1), excelDataConvertException.getCellData());
            throw new ApiException("第" (excelDataConvertException.getRowIndex() 1) "行,第" (excelDataConvertException.getColumnIndex()   1) "列格式转换错误");

        }
       throw new ApiException(exception.getMessage());
    }

    /**
     * 返回数据
     * @return 返回读取的数据集合
     **/
    public List<T> getDatas() {
        return datas;
    }

 
}

将监听器的每一行读取方法定义成抽象方法。

  1. 实现Mybatis-plus 真新增
代码语言:javascript复制
/**
 * @author :扫地僧
 * @date :2023/09/8 0008 10:59
 * @version: V1.0
 * @slogan: 天下风云出我辈,一入代码岁月催
 * @description: 批量插入 SQL 注入器
 **/
public class InsertBatchSqlInjector extends DefaultSqlInjector  {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        //更新时自动填充的字段,不用插入值
        methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
        return methodList;
    }
}
代码语言:javascript复制
@Configuration
public class MybatisPlusConfig {
    @Bean
    public InsertBatchSqlInjector sqlInjector() {
        return new InsertBatchSqlInjector();
    }
}
代码语言:javascript复制
public interface CommonMapper<T> extends BaseMapper<T> {
    /**
     * 全量插入,等价于insert
     * @param entityList
     * @return
     */
    int insertBatchSomeColumn(List<T> entityList);
}

将所有的mapper类本来继承BaseMapper的全都替换成 CommonMapper

  1. 定义分批List集合的工具类
代码语言:javascript复制
/**
 * @author :扫地僧
 * @date :2023/09/13 0013 15:21
 * @version: V1.0
 * @slogan: 天下风云出我辈,一入代码岁月催
 * @description: 通用分批次list集合工具
 **/
public  class ListBatcherUtils<T> {

    /**
     *
     * @param inputList 集合数据
     * @param batchSize 分批大小
     * @return
     */
    public List<List<T>> batchList(List<T> inputList, int batchSize) {
        List<List<T>> batchedLists = new ArrayList<>();
        for (int i = 0; i < inputList.size(); i  = batchSize) {
            int end = Math.min(i   batchSize, inputList.size());
            List<T> batch = inputList.subList(i, end);
            batchedLists.add(batch);
        }
        return batchedLists;
    }
}

分批的原因是:拦截器实现的是因为sql语句有大小限制,如果超了就会报错

  1. 具体导入代码
代码语言:javascript复制
@RestController
@RequestMapping("test/systest")
public class SysTestController {
    @Autowired
    private SysTestService sysTestService;
    /**
 * 模版导入
 * @param file
 * @return
 * @throws IOException
 */
    @PostMapping("upload")
    @ResponseBody
    public Result upload(MultipartFile file) throws IOException {
            return sysTestService.upload(file);
    }
}
代码语言:javascript复制
    @Override
    public Result upload(MultipartFile file) throws IOException {
      
        ExcelListener<SysTestEntityy> excelListener = new ExcelListener<SysTestEntity>() {
            private List<SysTestEntity> datas = getDatas();
            @Override
            public void invoke(SysTestEntityy data, AnalysisContext context) {
                //具体业务处理
                datas.add(data);
            }
        };
        //headRowNumber(1)从第2行开始读取,使用getDatas()方法取出数据
        EasyExcel.read(file.getInputStream(), OdsFpZzsXiaoxiangOrgEntity.class, excelListener)
                .headRowNumber(1)
                .sheet(0)
                .doRead();
        List<SysTestEntityy> list = excelListener.getDatas();
         //如果List集合数据小于1500不分批直接导入
        if (list.size() < 1500) {
            baseMapper.insertBatchSomeColumn(list);
            return ResultUtil.success("", "导入成功"   list.size()   "条");
        }
          //如果List集合数据大于1500分批导入
            ListBatcherUtils<SysTestEntityy> listBatcherUtils = new ListBatcherUtils<>();
            List<List<SysTestEntityy>> lists = listBatcherUtils.batchList(list, 1500);
            for (List<SysTestEntityy> sysTestEntityy : lists) {
                baseMapper.insertBatchSomeColumn(sysTestEntityy);
            }
        
    
        return ResultUtil.success("", "导入成功"   list.size()   "条");

    }

0 人点赞