首页 > 技术文章 > 使用eazyExcel读取数据结合mybatis批量保存到数据库(优化批量保存)

liweixml 2020-09-22 17:08 原文

easy-excel (mybatis oracle 批量插入sql优化)

数据量: 5万6 , 每行 30多个字段

执行平均用时: 30秒(看电脑配置)

 

准备依赖(2.1.1版本要求POI的版本必须为4.0及以上)

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
</dependency>

 

 

EazyExcelUtil

public class EazyExcelUtil {
    /**
     * 从Excel中读取文件,读取的文件是一个DTO类,该类必须继承BaseRowModel
     * 具体实例参考 : MemberMarketDto.java
     * 参考:https://github.com/alibaba/easyexcel
     * 字符流必须支持标记,FileInputStream 不支持标记,可以使用BufferedInputStream 代替
     * BufferedInputStream bis = new BufferedInputStream(new FileInputStream(...));
     */
    public static <T extends BaseRowModel> List<T> readExcel(final InputStream inputStream, final Class<? extends BaseRowModel> clazz) {
        if (null == inputStream) {
            throw new NullPointerException("the inputStream is null!");
        }
        ExcelListener<T> listener = new ExcelListener<>();
        // 这里因为EasyExcel-1.1.1版本的bug,所以需要选用下面这个标记已经过期的版本
        ExcelReader reader = new ExcelReader(inputStream,  valueOf(inputStream), null, listener);
        reader.read(new com.alibaba.excel.metadata.Sheet(1, 1, clazz));
        reader.finish();
        return listener.getRows();
    }
​
​
    public static void writeExcel(final File file, List<? extends BaseRowModel> list) {
        try (OutputStream out = new FileOutputStream(file)) {
            ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
            //写第一个sheet,  有模型映射关系
            Class<? extends BaseRowModel> t = list.get(0).getClass();
            Sheet sheet = new Sheet(1, 0, t);
            writer.write(list, sheet);
            writer.finish();
        } catch (IOException e) {
//            log.warn("fail to write to excel file: file[{}]", file.getName(), e);
            throw new SysException("写入excel 异常");
        }
    }
​
​
    /**
     * 根据输入流,判断为xls还是xlsx,该方法原本存在于easyexcel 1.1.0 的ExcelTypeEnum中。
     */
    public static ExcelTypeEnum valueOf(InputStream inputStream) {
        try {
            FileMagic fileMagic = FileMagic.valueOf(inputStream);
            if (FileMagic.OLE2.equals(fileMagic)) {
                return ExcelTypeEnum.XLS;
            }
            if (FileMagic.OOXML.equals(fileMagic)) {
                return ExcelTypeEnum.XLSX;
            }
            throw new IllegalArgumentException("excelTypeEnum can not null");
​
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
​
}

 

 

ExcelListener 监听器

public class ExcelListener <T extends BaseRowModel> extends AnalysisEventListener<T> {
​
    private final List<T> rows = new ArrayList<>();
​
    @Override
    public void invoke(T t, AnalysisContext analysisContext) {
​
        rows.add(t);
    }
​
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
​
    }
​
    public List<T> getRows() {
        return rows;
    }
}

 

 

DTO实体对应excel

public class Demo extends BaseRowModel {    //需要继承BaseRowModel
​
    @ExcelProperty(index = 0)   //指定字段对应excel的某列
    private String name;//get...set...
}

 

 

Dao层

public interfase DemoDao  { 
    //这里只是模拟
    void save(@Param("list") List<Demo> list);
    
}

 

 

mapper

<insert id="save">
    insert into demo (name)
    <foreach close=")" collection="list" item="item" index="index" open="(" separator="union">
        select
        #{item.name}
        from dual
    </foreach>
</insert>

 

 

service

@Service
@
Transactional
public class DemoService{
  
public void save(MultipartFile file, String year) {
    int divNum = 50;    //分组保存数量(有foreach的情况下20到50条的性能最好),否则使用单条保存
    List<ZfZhiFuLingData> list = null;
    //读取excel数据
    try {
           fis = file.getInputStream();
           list = EazyExcelUtil.readExcel(new BufferedInputStream(fis), Demo.class);
        } catch (IOException e) {
                throw new SysException("文件数据不符合规定,请重新上传" + e.getMessage());
        } finally {
            if (null != fis) {
                try {
                     fis.close();
                } catch (IOException e) {
                     e.printStackTrace();
            }
        }
     }
    //
    SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
    DemoDao mapper = session.getMapper(DemoDao.class);
    if (list.size() > 0) {
            //为了防止SQL语句超出长度出错,分成几次插入
            if (list.size() <= divNum) {
                mapper.save(list);
            } else {
                int length = list.size();
                // 计算可以分成多少组
                int num = (length + divNum - 1) / divNum;
                for (int i = 0; i < num; i++) {
                    // 开始位置
                    int fromIndex = i * divNum;
                    // 结束位置
                    int toIndex = (i + 1) * divNum < length ? (i + 1) * divNum : length;
                    mapper.save(list.subList(fromIndex, toIndex));
                }
            }
            session.flushStatements();
     }
  }
}
}

 

 

Controller层就自己写了

推荐阅读