EasyExcel
EasyExcel是阿里巴巴开源poi插件之一,主要解决了poi框架使用复杂,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。
主要解决方式:通过解压文件的方式加载,一行一行的加载,并且抛弃样式字体等不重要的数据,降低内存的占用。
EasyExcel优势
- 注解式自定义操作
- 输入输出简单,提供输入输出过程的接口
- 支持一定程度的单元格合并等灵活化操作
常用注解
- @ExcelProperty指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
- @ExcelIgnore默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
- @DateTimeFormat日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat
- @NumberFormat数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat
- @ExcelIgnoreUnannotated过滤属性没有@ExcelProperty注解的字段
依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
<exclusions>
<exclusion>
<artifactId>poi-ooxml-schemas</artifactId>
<groupId>org.apache.poi</groupId>
</exclusion>
</exclusions>
</dependency>
写
写完后通过web直接下载
controller层
@ResponseBody
@GetMapping("/studentDownload")
public String download(HttpServletResponse response){
studentService.writeToExcel(response);
return "download success";
}
service层
@Override
public void writeToExcel(HttpServletResponse response) {
// 使用swagger 会导致各种问题,请直接用浏览器或者用postman
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = null;
try {
fileName = URLEncoder.encode("学生表" + System.currentTimeMillis(), "UTF-8")
.replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
try {
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), Student.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("one").build();
int total = studentMapper.selectTotal();
int pages = total % NUMBER_OF_WRITES_PER_TIME == 0
? total / NUMBER_OF_WRITES_PER_TIME
: total / NUMBER_OF_WRITES_PER_TIME + 1;
System.out.println(pages);
for (int i = 1; i <= pages; i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<Student> data = dataList(i);
data.forEach(System.out::println);
excelWriter.write(data, writeSheet);
}
excelWriter.finish();
} catch (IOException e) {
e.printStackTrace();
}
}
访问
结果
读
通过web上传
@PostMapping("/studentUpload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
System.out.println("------upload--------------");
InputStream inputStream = file.getInputStream();
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(inputStream, Student.class, new StudentListener(studentService)).sheet().doRead();
return "upload success";
}
解析到每一条数据都会调用一次listener进行处理,需要的业务逻辑操作都写在listener内部即可
listener写法
此listener逻辑
- 每100条数据进行一次存储数据库操作
- 每条数据拿到后进行校验,校验不合法不放入list
/**
* @author QN
*/
@Slf4j
public class StudentListener implements ReadListener<Student> {
/**
* 每隔100条存储数据库,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<Student> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
private StudentService studentService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
*/
public StudentListener(StudentService studentService) {
this.studentService = studentService;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(Student data, AnalysisContext context) {
Gson gson = new Gson();
log.info("解析到一条数据:{}",gson.toJson(data) );
boolean b = studentService.registerCheck(data.getStu_no(), data.getStu_password());
if (b){
data.setStu_password(studentService.Digest(data.getStu_password()));
cachedDataList.add(data);
// 达到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());
studentService.saveBatch(cachedDataList);
log.info("存储数据库成功!");
}
}
结尾
更多详细操作,阅读EasyExcel官方文档