今天给大家来一个实战案例。
需求:把excel中的企业数据导入到数据库。
步骤:数据从excel中取出,封装成对象,保存到数据库
1.excel中的数据量大时,要保证执行效率
2.excel中可能会有重复数据
3.可能误操作,一个excel会导入多次,所以插入时需要检查数据库是否已存在当前要插入的数据了。
使用技术:easyPoi 官方文档:http://easypoi.mydoc.io/
cn.afterturn easypoi-spring-boot-starter 4.2.0 cn.afterturn easypoi-base 4.2.0 cn.afterturn easypoi-web 4.2.0 cn.afterturn easypoi-annotation 4.2.0
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
@ResponseBody
public R importExcel(@RequestParam("file") MultipartFile file) {
return enterprisesService.importExcel(file);
}
ImportParams params = new ImportParams();
//标题所在行数
// params.setTitleRows(0);
//表头所在行数
params.setHeadRows(1);
Map creditcodeAndEnterprisesMap = new HashMap<>();
//使用api获取到的List数据
ExcelImportUtil.importExcelBySax(file.getInputStream(), EnterprisesExcelModel.class, params, new IReadHandler() {
@Override
public void handler(Object o) {
EnterprisesEntity enterprisesEntity = new EnterprisesEntity();
BeanUtils.copyProperties(o, enterprisesEntity);
creditcodeAndEnterprisesMap.put(enterprisesEntity.getCreditcode(), enterprisesEntity);
}
@Override
public void doAfterAll() {
}
});
这里使用的是 easyPoi中的 ExcelImportUtil.importExcelBySax 工具方法 主要是用于处理大量数据的
第一个参数是 文件的input流
第二个是映射的实体类class,我的类中只有两个字段。用@Excel注解标注 name = excel中的对应的表头名称 。这里是一一对应的。
第三个是 导入的一些配置,这里 ImportParams 配置 表头所在行数 、标题所在行数。我这里无标题,表头在第一行。所以配置 params.setHeadRows(1);。也可以不配置,默认就是1
第四个参数是一个读取每一行后会触发的一个触发器。我直接使用了匿名内部类。在handler方法接受一个对象。该对象就是前面配置的 EnterprisesExcelModel的类对象。
我在方法里面又创建了一个 数据库映射实体类EnterprisesEntity 的对象,并把从excel 映射 解析来的 EnterprisesExcelModel对象字段值复制给了 EnterprisesEntity 对象。
然后存到map中 key存储 creditcode企业信用代码,因为这个是唯一的不会重复的。这么做可以去除excel中的可能存在的重复数据。
List creditcodeList = new ArrayList<>();
Set keys = creditcodeAndEnterprisesMap.keySet();
int i = 0;
for (String key : keys) {
creditcodeList.add(key);
if (i % 10000 == 0) {
//查询和过滤重复数据
queryAndDeleteDuplicateData(creditcodeAndEnterprisesMap, creditcodeList);
creditcodeList.clear();
}
i++;
}
if (creditcodeList.size() > 0) {
//查询和过滤重复数据
queryAndDeleteDuplicateData(creditcodeAndEnterprisesMap, creditcodeList);
}
遍历 map的key ,用一个list去存储creditcode企业信用代码每循环一万次 就需要拿着list 去数据库查询一次,这里的1万你们可以自行设置,这里主要是做限制,如果一次性一百万条的数据去查询数据库是否存在,那就崩了。。。
/**
* 查询和删除重复数据
*
* @param creditcodeAndEnterprisesMap map
* @param creditcodeList 要查询的 creditcode集合
*/
private void queryAndDeleteDuplicateData(Map creditcodeAndEnterprisesMap, List creditcodeList) {
List existEntitys = this.selectList(new EntityWrapper()
.in("CreditCode", creditcodeList).setSqlSelect("CreditCode")
);
log.info("查询出已存在的数据条数:" + existEntitys.size());
for (EnterprisesEntity existEntity : existEntitys) {
creditcodeAndEnterprisesMap.remove(existEntity.getCreditcode());
}
}
查询用的in,判断数据库有没有相同的creditCode 有的话取出来,是一个集合。
然后遍历该集合,从map中删除这些数据。
if (!creditcodeAndEnterprisesMap.isEmpty()) {
//保存数据,设置每次批量保存的条数
this.insertBatch(new ArrayList<>(creditcodeAndEnterprisesMap.values()), 10000);
}
1.要导入的excel文件大小超过20M,需要配置 application.yml
spring: # 设置单个文件大小 servlet: multipart: max-file-size: 100MB #设置单次请求文件的总大小 max-request-size: 100MB
2.为了提高mybatis-plush的批量保存效率,可以在配置文件中的 数据库地址后面增加参数rewriteBatchedStatements=true
原因:
MySQL Jdbc驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySQL数据库,直接造成较低的性能
性能测试:
1.只解析excel数据不做保存到数据库
2.解析+保存数据库
保存到数据库大约花费了300秒,期间最耗时的是查询数据库是否有数据重复这块。这块后续还是可以继续优化的。总体 6分钟百万数据,目前的业务还是可以接受的!
各位看官大佬有什么更好的建议欢迎留言指正!