官网 https://easyexcel.opensource.alibaba.com/
EasyExcel是阿里巴巴开源的,一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
其特点是:
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.1.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.20version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
①声明一个简单的对象
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
@ExcelProperty("字符串标题") //这里默认为value属性赋值,value为导出的表格的表头
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
@ExcelIgnore //忽略这个字段,生成的表格中就不会有该字段
private String ignore;
}
②写一个简单的表格导出案例
public class SimpleWriteTest {
@Test
public void simpleWrite(){
//1.模拟要写出数据
List<DemoData> list = ListUtils.newArrayList();
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);
}
//2.设置写出的文件路径
String fileName = "demo1.xlsx";
//3.利用EasyExcel写出,会自动关流
//write方法的参数1:文件路径,参数2:模板对象类型
EasyExcel.write(fileName, DemoData.class)
.sheet("模板") //sheet页名字
.doWrite(list); //要写出的数据
}
}
这样,就会在当前项目目录下生成名为demo1.xlsx
的表格,内容如下:
案例1:指定哪些属性不需要导出到表格中,仅导出剩余属性。
public class SimpleWriteTest {
@Test
public void excludeWrite(){
//1.模拟要写出数据
List<DemoData> list = ListUtils.newArrayList();
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);
}
//2.设置导出过程中剔除的字段
Set<String> excludeColumnFiledNames = new HashSet<String>();
excludeColumnFiledNames.add("date");//这里剔除date属性
//3.设置写出的文件路径
String fileName = "demo2.xlsx";
//4.写出
EasyExcel.write(fileName, DemoData.class)
.excludeColumnFieldNames(excludeColumnFiledNames) //将要剔除的列设置给EasyExcel
.sheet("模板")
.doWrite(list);
}
}
案例2:指定仅导出哪些属性,剩余属性不导出。
public class SimpleWriteTest {
@Test
public void IncludeWrite(){
//1.模拟要写出数据
List<DemoData> list = ListUtils.newArrayList();
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);
}
//2.设置仅导出哪些属性
Set<String> includeColumnFiledNames = new HashSet<String>();
includeColumnFiledNames.add("date");
//3.设置写出的文件路径
String fileName = "demo3.xlsx";
//4.写出
EasyExcel.write(fileName, DemoData.class)
.includeColumnFieldNames(includeColumnFiledNames) //设置给EasyExcel
.sheet("模板")
.doWrite(list);
//这里导出的表格中就只有:时间字段。
}
}
使用@ExcelProperty
注解的index
属性指定要导入列的下标。
@Getter
@Setter
@EqualsAndHashCode
public class IndexData {
@ExcelProperty(value = "字符串标题", index = 0)
private String string;
@ExcelProperty(value = "日期标题", index = 1)
private Date date;
//这里设置3 会导致第三列空的
@ExcelProperty(value = "数字标题", index = 3)
private Double doubleData;
}
public class WriteTest {
@Test
public void writeDemo(){
//1.模拟要写出数据
List<IndexData> list = ListUtils.newArrayList();
for (int i = 0; i < 10; i++) {
IndexData data = new IndexData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
//2.设置写出的文件路径
String fileName = "demo4.xlsx";
//3.写出
EasyExcel.write(fileName, IndexData.class)
.sheet("模板")
.doWrite(list);
}
}
在导出表格的时候,会因为业务需求,导出复杂的表头,咱们可以使用@ExcelProperty
注解的value
属性来实现这个需求。
例如我们要导出一个如图的表格:
仅需通过value
属性设置即可:
@Getter
@Setter
@EqualsAndHashCode
public class ComplexHeadData {
@ExcelProperty(value={"主标题", "副标题1","字符串标题"})
private String string;
@ExcelProperty(value={"主标题", "副标题1","日期标题"})
private Date date;
@ExcelProperty(value={"主标题", "副标题2","数字标题"})
private Double doubleData;
}
导出的代码:
public class WriteTest {
@Test
public void complexHeadWrite() {
List<ComplexHeadData> list = ListUtils.newArrayList();
for (int i = 0; i < 10; i++) {
ComplexHeadData data = new ComplexHeadData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
//2.设置写出的文件路径
String fileName = "demo5.xlsx";
//3.导出
EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(list);
}
}
public class WriteTest {
@Test
public void repeatedWrite() {
//1.模拟要写出数据
List<DemoData> list = ListUtils.newArrayList();
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);
}
String fileName = "demo6.xlsx";
//2.创建 ExcelWriter对象,并指定文件名和对象类型
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
// 这里模拟5次
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet,这里注意必须指定sheetNo(sheet页编号),而且sheetName(sheet也名字)必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
// 将数据写入到sheet页中,最终,会生成5个sheet页
excelWriter.write(list, writeSheet);
}
}
}
}
public class WriteTest {
@Test
public void repeatedWrite1() {
//1.模拟要写出数据
List<DemoData> list = ListUtils.newArrayList();
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);
}
String fileName = "demo7.xlsx";
//2.创建ExcelWriter对象,仅需指定文件名,因为写入的数据是不固定的,所以不能写死
try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
// 模拟5次
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。
// 注意:head方法中的DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class,实际上可以一直变
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
// 写出数据
excelWriter.write(list, writeSheet);
}
}
}
}
在写出数据时,如果需要对数据进行格式设置,可以为对象进行如下设置:
@Getter
@Setter
@EqualsAndHashCode
public class ConverterData {
/**
* 我想所有的 字符串起前面加上"自定义:"三个字
*/
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
/**
* 我想写到excel 用年月日的格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
/**
* 我想写到excel 用百分比表示
*/
@NumberFormat("#.##%")
@ExcelProperty(value = "数字标题")
private Double doubleData;
}
自定义格式:
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 这里读的时候会调用,先不管
*/
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
return "自定义:" + context.getReadCellData().getStringValue();
}
/**
* 这里是写的时候会调用
* 会在原有内容之前拼接上:"自定义:"
*/
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
return new WriteCellData<>("自定义:"+context.getValue());
}
}
写出的代码:
public class SimpleWriteTest {
@Test
public void converterWrite() {
//1.模拟要写出数据
List<ConverterData> list = ListUtils.newArrayList();
for (int i = 0; i < 10; i++) {
ConverterData data = new ConverterData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
String fileName = "demo8.xlsx";
//3.写出
EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(list);
}
}
使用注解在对象的属性上进行设置即可
@Getter
@Setter
@EqualsAndHashCode
@ContentRowHeight(20) //内容高度为:20
@HeadRowHeight(40) //表头高度为:40
@ColumnWidth(25) //列宽为:25
public class WidthAndHeightData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
//单独设置宽度为50
@ColumnWidth(50)
@ExcelProperty("数字标题")
private Double doubleData;
}
导出代码为:
public class WriteTest {
@Test
public void widthAndHeightWrite() {
//1.模拟要写出数据
List<WidthAndHeightData> list = ListUtils.newArrayList();
for (int i = 0; i < 10; i++) {
WidthAndHeightData data = new WidthAndHeightData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
String fileName = "demo9.xlsx";
//3.写出
EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(list);
}
}
使用注解来设置合并规则:
@Getter
@Setter
@EqualsAndHashCode
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
// 这一列 每隔2行 合并单元格
@ContentLoopMerge(eachRow = 2)
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}
写出:
public class SimpleWriteTest {
@Test
public void mergeWrite() {
//1.模拟要写出数据
List<DemoMergeData> list = ListUtils.newArrayList();
for (int i = 0; i < 10; i++) {
DemoMergeData data = new DemoMergeData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
String fileName = "demo10.xlsx";
//2.写出
EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(list);
}
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<a href="/download">导出excela>
body>
html>
@Controller
public class DownloadController {
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException {
//1.模拟数据
List<DemoData> list = ListUtils.newArrayList();
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);
}
//2.执行导出
//设置响应类型
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//设置编码
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止文件名中文乱码,当然和easyexcel没有关系
String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
//设置文件名
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//导出
EasyExcel.write(response.getOutputStream(), DemoData.class).sheet("模板").doWrite(list);
}
}
easyexcel读取表格内容是基于监听器方式实现的,我们需要实现它提供的ReadListener
接口,进而实现读取的功能。
假如有表格demo.xlsx
的内容如下:
① 首先我们需要准备一个对象与之对应:
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}
② 准备监听器
public class DemoDataListener implements ReadListener<DemoData> {
/**
* 每存储100条,可以存储一次数据库,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 用于缓存的数据的集合
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/* 在实际开发中我们可以使用有参构造,将自己的 dao 或者 service传进来,进而实现数据入库
private DemoDAO demoDAO;
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
*/
/**
* 这个每一条数据解析都会来调用
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
System.out.println("每次读到内容是:"+data);
cachedDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
//1.存储数据库
//demoDAO.saveBatch(cachedDataList);
//2.存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 所有数据解析完成了 都会来调用
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("所有数据解析完成!");
}
}
③ 读取表格内容
public class ReadTest {
@Test
public void simpleRead() {
String fileName = "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
}
@PostMapping("/upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
//read方法参数说明:
//参数1:文件输入流
//参数2:与表格数据对应的对象类型
//参数3:监听器
EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO))
.sheet()
.doRead();
return "success";
}