1. 需求:页面上传16万条数据的excel, 后台一些过滤操作后存表
一开始我想,就用就从常用的 JavaPOI导出Excel有三种形式选一个: HSSFworkbook,XSSFworkbook,SXSSFworkbook
但是 实践过程中发现,事实并不是想象的那么随意,实践结果是 这三个都不能使用
HSSFworkbook ,XSSFworkbook : 太小,解析导入的excel会报 内存溢出
SXSSFworkbook: 不是用于解析导入,只适用于导出,也不符合条件
想了解这三种方式的朋友可以参考:参考文档:https://blog.csdn.net/YiQ2018/article/details/81458149
Workbook wb = WorkbookFactory.create(file.getInputStream());这种方式仍然是行不通,等待时间无尽头
各种百度:发现了 EXCEL 转 CSV 处理
这种方式就是过程有点复杂,还要去实现接口,想了解的朋友可以参考:
参考文档:https://blog.csdn.net/wanglizheng825034277/article/details/88662502
最终:发现新大陆:流方式读取 Excel2007-Excel07SaxReader
使用方式也是相当的容易,下面上步骤:
1. 项目中添加依赖:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.3.0</version>
</dependency>
使用示例:我上传的是只有手机号的一列, 对手机号进行处理校验
第一个参数可以传 文件、流、string等,想怎么传就怎么传。第二个参数是sheet的序号,-1表示读取所有sheet,0表示第一个sheet,依此类推。第三个就是处理逻辑
Set<String> mobiles = new HashSet<>();
Excel07SaxReader reader = ExcelUtil.read07BySax(file, 0, new RowHandler() {
@Override
public void handle(int sheetIndex, int rowIndex, List<Object> rowlist) {
String mobile = rowlist.get(0).toString().trim();
int size = rowlist.size();
if (rowIndex == 0) {
if (!mobile.equals("手机号") || size != 1) {
try {
throw new Exception("模板错误!请重新导入");
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
if (mobile.length() != 11) {
log.error("文件名:"+fileName + "第" + rowIndex + "行手机号有误!" + "消息Id:" + noticeId);
} else {
mobiles.add(rowlist.get(0).toString());
}
}
}
});
参考链接;https://hutool.cn/docs/#/
存库问题: 16万数据 存入库。
我的项目用是mybatis plus, 不管用的是哪一种,虽然都封装了 批量导入,但是底层 插入是一条一条的存入数据库,这样一来,频繁的和数据库进行交互,导致性能很慢。
解决方案:尝试用 insert 。。。values(),(),(). 这样一来,确实快了不少,需要注意的是, values 后面也不能无限的追加, 凡事过犹不及。 使用分批 插入,一万 一万的存储。同时开一个单线程进行处理保存。这里特别说明一下,不要想着开多线程就会更快一点,其实并不。开多线程会占用更多的内存,导致内存溢出。
如此还会有新的问题,就是 堆内存溢出的问题。
由于处理,存储均在一个service中进行,导致内存不能得到释放,出现内存溢出错误,因此,又出来一个新的解决方法,就是将处理逻辑和保存分开来,在controller层进行调用。这样就解决了内存溢出的问题。不足之处就是不能在保证在同一个事务中进行,但是这个可以忽略掉。。
Controller 层
// --->从S3桶 里面 获取上传的文件
List<NoticeReceiverTemp> noticeReceiverTempList = noticeReceiverTempService.selectList(new EntityWrapper<NoticeReceiverTemp>().eq("uuid", uuid));
for (NoticeReceiverTemp temp : noticeReceiverTempList) {
List<String> mobiles = noticeReceiverService.dealOneFile(temp.getUuid(), temp.getFileName(), temp.getFileKey(), tenantId, noticeMainDTO.getReceiveType(), user.getUser_id(), noticeId);
noticeReceiverService.oneFile(mobiles,noticeMainDTO.getReceiveType(),user.getUser_id(),temp.getFileName(),uuid, noticeId);
}
Service 层
@Override
public List<String> dealOneFile(String uuid, String fileName, String key, String tenantId, Integer receiveType, Long userId, Long noticeId) throws Exception {
long beginTime = System.currentTimeMillis();
Set<String> mobiles = new HashSet<>();
log.info("消息中心导入会员开始时间:" + beginTime);
InputStream in = s3Util.amazonS3Downloading(s3Util.getS3Client(), CrmConstants.AWS_BUCKET_NAME_CRM + "/msg", key, "");
Excel07SaxReader reader = ExcelUtil.read07BySax(in, 0, new RowHandler() {
@Override
public void handle(int sheetIndex, int rowIndex, List<Object> rowlist) {
String mobile = rowlist.get(0).toString().trim();
int size = rowlist.size();
if (rowIndex == 0) {
if (!mobile.equals("手机号") || size != 1) {
try {
throw new Exception("模板错误!请重新导入");
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
if (mobile.length() != 11) {
log.error("文件名:"+fileName + "第" + rowIndex + "行手机号有误!" + "消息Id:" + noticeId);
} else {
mobiles.add(rowlist.get(0).toString());
}
}
}
});
if (mobiles.size() < 1) {
throw new Exception("文件内容不能为空,请重新上传!");
}
List<String> uploadMobiles = new ArrayList<>(mobiles);
return uploadMobiles;
}
@Override
public void oneFile(List<String> uploadMobiles,Integer receiveType,Long userId,String fileName,String uuid,Long noticeId) throws Exception {
ExecutorService executorService= Executors.newSingleThreadExecutor();
executorService.submit(()-> {
int batchSize = 10000;
if (uploadMobiles.size() > batchSize) {
int size = 0;
if (uploadMobiles.size() % batchSize == 0) {
size = uploadMobiles.size() / batchSize;
} else {
size = uploadMobiles.size() / batchSize + 1;
}
for (int i = 0; i < size; i++) {
if (i != size - 1) {
// 一次处理一万条,同时存表
try {
addReceiversDeal(receiveType, noticeId, userId, uploadMobiles.subList(i * batchSize, batchSize * (i + 1)), fileName, uuid);
} catch (Exception e) {
e.printStackTrace();
}
} else {
// 一次处理一万条,同时存表
try {
addReceiversDeal(receiveType, noticeId, userId, uploadMobiles.subList(i * batchSize, uploadMobiles.size()), fileName, uuid);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} else {
try {
addReceiversDeal(receiveType, noticeId, userId, uploadMobiles, fileName, uuid);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* 分批存一万
*
* @param receiveType
* @param noticeId
* @param userId 187@param noticeReceiverMobiles
* @param fileName
* @param uuid
*/
private void addReceiversDeal(Integer receiveType, Long noticeId, Long userId, List<String> noticeReceiverMobiles, String fileName, String uuid) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<NoticeReceiver> receivers = new ArrayList<>(noticeReceiverMobiles.size());
for (String mobile : noticeReceiverMobiles) {
receivers.add(new NoticeReceiver(noticeId, receiveType, mobile, 0, userId, format.format(new Date()), fileName, uuid));
}
if (!CollectionUtils.isEmpty(receivers)) {
noticeReceiverMapper.insertBatchByMySelf(receivers);
receivers = null;
}
}
内存使用对比图:单线程保存
多线程保存(开2个线程 就会报堆内存溢出错误)
到此结束!希望帮到你