优化EasyExcel导入

首页 / SpringBoot / 正文

优化EasyExcel导入

EasyExcel 导入可以参考我这篇文章 http://nwjshm.cn/archives/48.html

在真实场景的情况下Excel数据量很庞大,如果需要做一些字典转换,io一次数据库的时间挺大的

优化思路

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

实现

  1. 重构EasyExcel监听器
@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 真新增
/**
 * @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;
    }
}
@Configuration
public class MybatisPlusConfig {
    @Bean
    public InsertBatchSqlInjector sqlInjector() {
        return new InsertBatchSqlInjector();
    }
}
public interface CommonMapper<T> extends BaseMapper<T> {
    /**
     * 全量插入,等价于insert
     * @param entityList
     * @return
     */
    int insertBatchSomeColumn(List<T> entityList);
}
将所有的mapper类本来继承BaseMapper的全都替换成 CommonMapper
  1. 定义分批List集合的工具类
/**
 * @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. 具体导入代码
@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);
    }
}
    @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() + "条");

    }
评论区
头像
    头像

    你好,看完你的博客文章,感觉很不错!希望与你网站首页友情链接
    流量卡知识网
    http://53go.cn/
    专注于移动 / 联通 / 电信运营商官方推出的大流量多语音活动长短期套餐手机卡的相关知识的介绍普及

    听说互换友情链接可以增加网站的收录量,特此来换,如果同意的话就给 internetyewu@163.com[微信 ganenboy] 发信息或者就在此回复下吧!【建站问题也可以一起讨论!】