Easy Excel
EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
EasyExcel底层对POI进行了封装,重写了poi的方法。在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析,遇到再大的excel都不会出现内存溢出的问题,能够将一个原本3M的excel文件,POI来操作将会占用内存100M,使用EasyExcel降低到几KB,并且使用起来更加简单。
数据导入导出执行原理和思路:
用户端逻辑:
1. 数据导入用户先下载模板,根据模板填入数据,然后点击上传;
2. 数据导出
用户在界面选择需要导出的数据(导出条件),点击导出。
后台开发逻辑:
1. 模板下载利用easyExcel生成文件,然后将文件放进响应流中,同时设置响应头为文件下载,浏览器收到响应之后,回去解析流中的内容,然后进行下载。
2. 文件上传
用在填写好Excel内容之后,会以文件上传的形式,将文件上传到服务端,此时,我们只需要利用EasyExcel将文件流中的数据读出来即可。
3. 数据导出
后台在接收到用户的数据导出请求之后,会根据请求中的筛选条件,查询对应数据,再将对应的数据填充进对应的导出模板中,以流的形式响应给浏览器。其实和模板下载的差不错,只是模板下载没有数据,数据导出有数据而已。
导入easyexcel依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
导出功能
实体类
*/
@TableName(value ="score")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Score implements Serializable {
public Score(String name, String xuehao, String score) {
this.name = name;
this.xuehao = xuehao;
this.score = score;
}
/**
*
*/
@ExcelProperty("ID")
@TableId(type = IdType.AUTO)
private Integer id;
/**
*
*/
@ExcelProperty("姓名")
private String name;
/**
*
*/
@ExcelProperty("学号")
private String xuehao;
/**
*
*/
@ExcelProperty("成绩")
private String score;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
后端代码
@RequestMapping("/index/load")
@ResponseBody
public String load(HttpServletResponse response) throws Exception {
List<Score> list = scoreMapper.selectList(null);
if(list==null)
return "error";
String filename=System.currentTimeMillis()+"";
ServletOutputStream servletOutputStream = servletOutputStream(filename, response);
EasyExcel.write(servletOutputStream,Score.class).sheet("成绩信息").doWrite(list);
servletOutputStream.flush();
return "success";
}
public static ServletOutputStream servletOutputStream(String filename,HttpServletResponse response) throws Exception {
try {
filename = URLEncoder.encode(filename, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
response.setHeader("Content-Disposition", "attachment; filename=" + filename + ".xlsx");
response.setHeader("Pragma", "public");
response.setHeader("Cache-Control", "no-store");
response.addHeader("Cache-Control", "max-age=0");
return response.getOutputStream();
} catch (IOException e) {
throw new Exception("导出excel表格失败!", e);
}
}
前端vue代码
<el-button type="primary" style="margin: 10px " @click="daochu">
<template #icon>
<el-icon><Download /></el-icon>
</template>
导出</el-button>
const daochu=()=>{
window.open('http://localhost:8080/index/load')
}
导入功能
监听器
package com.example.demo.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.example.demo.mybatisx.entity.Score;
import com.example.demo.mybatisx.mapper.ScoreMapper;
import com.example.demo.mybatisx.service.ScoreService;
import com.example.demo.utils.DemoDAO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<Score> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<Score> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
private ScoreMapper scoreMapper;
private ScoreService scoreService;
public DemoDataListener(ScoreService scoreService) {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
this.scoreService=scoreService;
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param score one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(Score score, AnalysisContext context) {
score.setId(null);
System.out.println(score);
cachedDataList.add(score);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
scoreService.saveBatch(cachedDataList);
log.info("存储数据库成功!");
}
}
后端代码 MultipartFile是spring里定义的接口,它封装了用户在上传文件时所包含的所有信息。
@RequestMapping("/index/import")
@ResponseBody
public String imp( @RequestParam("file") MultipartFile multipartFile) throws IOException {
InputStream inputStream = multipartFile.getInputStream();
EasyExcel.read(inputStream,Score.class,new DemoDataListener(scoreService)).sheet().doRead();
return "success";
}
Vue 前端代码
<el-upload
action="http://localhost:8080/index/import"
style="display: inline-block"
:show-file-list="false"
:on-success="importsuccess"
name="file"
>
<el-button type="primary" style="margin: 10px ">
<template #icon>
<el-icon><Upload /></el-icon>
</template>
导入</el-button>
</el-upload>
const importsuccess=(response,file)=>{
console.log(response,file)
alert("导入成功")
load()
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/143100.html