使用eazyExcel读取数据结合mybatis批量保存到数据库(优化批量保存)
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层就自己写了