欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot+easypoi大数据Excel导出

程序员文章站 2024-03-21 20:16:46
...

刚开始以为这个很简单,但是做的时候却遇到很多坑。特此记录一下,整理一下自己的解决方法,希望也可以帮助到其他人,如有不合理的地方,请望指出,不胜感激。
第一个坑:数据量过大,使用数据库查询(还是已分表的多表查询)拿取太慢了
这个很耗时,之前我以为通过数据优化的方式就可以解决,太年轻,数据量庞大,它的偏移量就是那么大,所以耗时还是无法解决。没办法,只能借助其他工具,因为我的导出数据是日志数据,即它不怎么变化,所以我使用了mongodb去存储我的数据。OK,数据拿取很快了。
第二个坑:内存泄漏
这个坑我是做之前就知道会存在的,毕竟那么大的数据量是很占服务器资源的,单个进程任务的数据量过大,而又无法及时回收到系统内存,最终导致系统内存耗尽而宕机。easypoi和mongodb都有相应的处理。
mongodb可以通过分页的方式去拿取数据,可以控制每次拿取的数据量:
嘿嘿,都分页了,所以这里还可以用多线程去做哦。

// pageSize 自定义取出大小
public Workbook excelWorkBook(Query query,Integer pageNum){
		//分页拿取数据
		Pageable pageable = new PageRequest(pageNum,pageSize);
		query.with(pageable);
		List<Test> dataList = mongoTemplate.find(query,Test.class,collectionName);
		if (!CollectionUtils.isEmpty(dataList)){
			workbook =  ExcelUtil.exportBigExcel(params, Test.class, dataList);
		}
		logger.info("第"+pageNum+ "页的"+"的真实数据量大小为:"+ dataList.size());
		if (!pageSize.equals(dataList.size())){
			pageNum++;
			workbook = excelWorkBook(query,pageNum);
		}
		return workbook;
	}
	
public static Workbook exportBigExcel(ExportParams entity, Class<?> pojoClass, Collection<?> dataSet) {
		//批量处理数据
        ExcelBatchExportService batchService = ExcelBatchExportService.getExcelBatchExportService(entity, pojoClass);
        return batchService.appendData(dataSet);
    }	

第三个坑:网关时间限制
哎,我这个服务是走了网关的,网关一直报超时,我当时就想把这个时间限制关了,但是这样做未免太不明智了,还好柳暗花明又一村,既然超时那我就骗过网关就好啦,自然而然,异步就是一个很好的解决方式。bingo,springboot还自带异步处理@Async,这篇博客里的讲的很不错,可以看下这个>https://www.cnblogs.com/huanzi-qch/p/11231041.html具体去实现。
很好很好,超时问题解决了,但是前端怎么拿到我的文件呢。因为这个服务已经用到了阿里云的OSS,所以我这里用的是OSS,也可以暂时放在服务器上。当后台把文件导出处理好了,并且上传到了服务器,可以通过websocket或者接口轮询的方式去通知前端可以去服务器上下载文件了。

// 上传
public void handleWorkBook(Workbook workbook,String fileName){
	InputStream fileContent = null;
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			workbook.write(bos);
			byte[] barray = bos.toByteArray();
			fileContent = new ByteArrayInputStream(barray);
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			//我这里是通过OSS存储文件的,用redis存储这个文件的过期时间
			ossManageUtil.uploadFile(fileContent,fileName);
		} catch (Exception e) {
				e.printStackTrace();
		}		
}

这个文件是一次性的,可以通过redis的监视器去删除文件,要不然浪费存储空间资源。

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener  {
	@Autowired
	private OSSManageUtil ossManageUtil;

	public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
		super(listenerContainer);
	}
	@Override
	public void onMessage(Message message, byte[] pattern) {
		String expiredKey = message.toString();
		if(expiredKey.startsWith("ExportExcel_")) {
			String key = expiredKey.replace("ExportExcel_", "");
			try {
				ossManageUtil.deleteFile(key);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

还有很多优化的点,几百万的数据量可以分文件再压缩或者多sheet导出,可以用多线程加快导出速度,可以用任务队列来控制这个并发等等。这些实现的过程可能又会遇到很多坑哦,哈哈。