1.EasyExcel介绍:EasyExcel是阿里巴巴开源的一个excel处理框架。
2.特性:使用简单、节省内存、适合处理大数据量的Excel。
数据解析时,不像之前的Apache poi、jxl等Excel解析框架将数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。EasyExcel重写了poi对07版本Excel的解析,在数据量大时不容易内存溢出。
3.使用场景
(1)数据导入:减轻录入工作量
(2)数据导出:统计信息归档
(3)数据传输:异构系统之间数据传输
4.官方文档:https://alibaba-easyexcel.github.io/index.html
这里我也是参照官方文档做的,但也踩过坑,我这里介绍的会比官方文档更细致一些。我这里重点关注写的使用。
1.首先就是需要一个SpringBoot的项目,引入依赖,我这里用的最新的版本,注意如果有poi的依赖,会有冲突,需要移除掉。
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.0.5version>
dependency>
2.实现多sheet的写功能
注意这里不要和web的写弄混淆了。这里的往指定文件写入数据。
(1)创建一个Controller,这里需要一个实体类,一个程序入口,为了方便我就写在一起了。数据方面使用官网给的默认数据。有区别的是,我没使用lombok的@Data注释。新增的sheetName字段,不然按官方的例子全写到一个sheet页了。
(2)创建文件demoData.xlsx,并放入指定目录里面。
package com.example.springb_web.easyexcel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
public class EasyExcelController {
private String templatePath = "D:/Tools/easyExcelTemplate/";
@RequestMapping(value="/exportTest")
public void export_multi_sheet(HttpServletResponse response){
String template = templatePath+"demoData.xlsx";
// 指定模板文件
ExcelWriter excelWriter = EasyExcel.write(template, DemoData.class).build();
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo和sheetName
String sheetName = "模板"+(i+1);
WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
// 千万别忘记finish 会帮忙关闭流
excelWriter.finish();
}
private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}
class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Double getDoubleData() {
return doubleData;
}
public void setDoubleData(Double doubleData) {
this.doubleData = doubleData;
}
public String getIgnore() {
return ignore;
}
public void setIgnore(String ignore) {
this.ignore = ignore;
}
}
(3)验证,进入http://localhost:8080/exportTest,在查看demoData.xlsx,结果如下。
3.实现web的写功能
直接使用官方的方法,我测试过没有问题,需要引入fastJson的依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.28version>
dependency>
@GetMapping("downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), DemoData.class).autoCloseStream(Boolean.FALSE).sheet("模板")
.doWrite(data());
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = new HashMap<String, String>();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
4.实现web的多sheet写功能
(1)完整代码如下,新增实现方法webMultiSheetWrite、增加标题的getHead和切割list的方法subListUtil。
package com.example.springb_web.easyexcel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.*;
@RestController
public class EasyExcelController {
private String templatePath = "D:/Tools/easyExcelTemplate/";
@RequestMapping(value="/exportTest")
public void export_multi_sheet(HttpServletResponse response){
String template = templatePath+"demoData.xlsx";
// 指定模板文件
ExcelWriter excelWriter = EasyExcel.write(template, DemoData.class).build();
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo和sheetName
String sheetName = "模板"+(i+1);
WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
// 千万别忘记finish 会帮忙关闭流
excelWriter.finish();
}
@GetMapping("downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), DemoData.class).autoCloseStream(Boolean.FALSE).sheet("模板")
.doWrite(data());
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = new HashMap<String, String>();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
@GetMapping("webMultiSheetWrite")
public void webMultiSheetWrite(HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("多sheet写测试", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
//设置每个sheet存3条数据
int n = 3;
//将数据拆分成多个sheet页
List<List<DemoData>> userlistBysheet = subListUtil(data(),n);
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).head(getHead("大标题")).build();
for (int i = 0; i < userlistBysheet.size(); i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo
String sheetName = "模板"+(i+1);
WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).build();
List<DemoData> data = userlistBysheet.get(i);
excelWriter.write(data, writeSheet);
}
excelWriter.finish();
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = new HashMap<String, String>();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
private List<List<String>> getHead(String bigTitle){
List<List<String>> head = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<>();
head0.add(bigTitle);
head0.add("字符串标题");
List<String> head1 = new ArrayList<>();
head1.add(bigTitle);
head1.add("日期标题");
List<String> head2 = new ArrayList<>();
head2.add(bigTitle);
head2.add("数字标题");
head.add(head0);
head.add(head1);
head.add(head2);
return head;
}
//切割list方法
public <T> List<List<T>> subListUtil(List<T> source, int n) {
List<List<T>> result = new ArrayList<>();
//(先计算出余数)
int remainder = source.size() % n;
//然后是商
int number = source.size() / n;
//偏移量
int offset = 0;
List<T> value;
for (int i = 0; i < number; i++) {
value = source.subList(offset, offset + n);
offset += n ;
result.add(value);
}
value = source.subList(offset, offset + remainder);
result.add(value);
return result;
}
private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}
class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Double getDoubleData() {
return doubleData;
}
public void setDoubleData(Double doubleData) {
this.doubleData = doubleData;
}
public String getIgnore() {
return ignore;
}
public void setIgnore(String ignore) {
this.ignore = ignore;
}
}
(2)测试
进入http://localhost:8080/webMultiSheetWrite,可以看到十条数据,每三条一个sheet页,被正确拆分了。
1.@ExcelProperty
@ExcelProperty("标题")
@ExcelProperty(value="标题",index=0)
value用来自定义表头(二级表头只需要使用 {一级表头,二级表头} 来实现),index用来选择excel中的顺序(第一排为0)
2.@ExcelIgnore
在实际应用中,修改存量的程序,对象中不需要导出的多余的属性,需要使用@ExcelIgnore注解,否则会导出很多你不需要的字段。
4.@DateTimeFormat
日期格式转换
4.@ColumnWidth
设置此属性的行宽
1.ExcelDataConverException:Cannot find ‘Converter’ support class
比如提示你日期格式无法导出
解决方案:
在需要转换字段较少的情况下使用方案1较为简便,长久考虑还是方案2比较好。
(1)在对象中新增一个字段,以字符串存储,修改如下:
@ExcelProperty
private Date date;
改成这个,然后在list中转行给date_str赋值
@ExcelIgnore
private Date date;
@ExcelProperty
private String date_str;
(2)创建一个类,实现 Converter<> 接口,泛型为需要转换的类型 ,并重写其中的方法。
public class LocalDateTimeConverter implements Converter<LocalDateTime> {
@Override
public Class<LocalDateTime> supportJavaTypeKey() {
return LocalDateTime.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
//将格式化后的日期转换为字符串
@Override
public LocalDateTime convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
//将LocalDateTime类型格式化
@Override
public CellData<String> convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData<>(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}
然后在@ExcelProperty标签下增加该Converter类
@ExcelProperty(
converter = LocalDateTimeConverter.class
)