easyExcel的基本使用步骤(纯入门操作)
首先:吃水不忘挖井人,更多详细操作见EasyExcel官方使用说明文档。
- 导入pom依赖
关键依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
其他依赖,不必须,但本人新建的普通项目缺少会报错,视情况而定。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
- 开始使用
导出和导入Excel的基本使用。
导出
使用实例
- excel实体对象(指明了包含的表头)
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.superman.test.connnnn.Connvv;
import lombok.Data;
import java.util.Date;
@Data
@ContentRowHeight(20)
@HeadRowHeight(50)
public class DemoData {
//指定值和出现的列数,从0开始的哦
//converter的作用是自定义导入导出的规则,比如这里我想将
@ExcelProperty(value = {"主标题","字符串标题"},index = 0,converter = Connvv.class)
@ColumnWidth(25)
private String string;
//自定义日期的显示格式
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty(value = {"主标题","日期标题"},index = 1)
@ColumnWidth(30)
private Date date;
@ExcelProperty(value = {"主标题","数字标题"},index = 2)
@ColumnWidth(25)
private Double doubleData;
/**
* 忽略这个字段
*/
// @ExcelIgnore
@ExcelProperty(value = "忽略字段重出江湖",index = 3)
//指定宽度
@ColumnWidth(40)
private String ignore;
}
- 根据实体类去生成一个Excel文件
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.superman.test.entity.DemoData;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@SuppressWarnings("all")
public class TestExcel {
//生成List数据
private static 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);
data.setIgnore("时而忽略,时而不忽略,你觉得忽略不忽略?");
list.add(data);
}
return list;
}
public static void main(String[] args) throws IOException {
File file = new File("superman1.xlsx");
// 最简单的写法1
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(file, DemoData.class).sheet("模板").doWrite(data());
// 最简单的写法2
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(file, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板001").build();
//这个data在实际操作中是可以分页查询数据库操作的
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
} finally {
// 写法2千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
}
注意:下面的特殊使用示例均为main方法下的,直接拷贝到main方法就可以测试了,相关后面要在实体类上使用的注解都已经在上面的实体类上全部添加了,下面会在使用到的时候挨个解释直接的使用含义。
- 生成Excel的部分特殊使用需求示例:
- 指定写入的列:
在实体类属性的注解上指定index列,例如这边指定的就是第1列(索引从0开始):
@ExcelProperty(value = "字符串标题",index = 0)
private String string;
这就实现了string
属性在第1
列显示。
- 复杂头写入:
在实体类属性注解上使用{总表头,xx}
的方式,即可实现第一行显示总表头,第二行开始照旧,按列写入后面的xx
属性,例如:
@ExcelProperty(value = {"主标题","字符串标题"},index = 0)
private String string;
@ExcelProperty(value = {"主标题","日期标题"},index = 1)
private Date date;
@ExcelProperty(value = {"主标题","数字标题"},index = 2)
private Double doubleData;
@ExcelProperty(value = "忽略字段重出江湖",index = 3)
private String ignore;
- 根据已知模板写入Excel,例如:
File file = new File("superman1.xlsx");
//使用模板的写法,先生成一个临时文件,将临时文件作为模板
File file1 = new File(System.getProperty("java.io.tmpdir")+"qwe.xlsx");
//上面新建的一个临时的文件,下面向临时文件写入Excel数据从而生成临时模板Excel
EasyExcel.write(file1, DemoData.class).sheet("模板666").doWrite(data());
File tempFile = new File(file1.getCanonicalPath());
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(file, DemoData.class).withTemplate(tempFile).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.write(data(), writeSheet);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
//删除临时文件
file1.delete();
tempFile.delete();
}
- 合并单元格,例如将第6-7行的2-3列合并成一个单元格(这些个注解是加在类上的):
方式一:在类上添加即可
@OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
方式二: 这一列 每隔2行 合并单元格,在属性上添加
@ContentLoopMerge(eachRow = 2)
导入
使用示例
- Excel文件示例
字符串标题 | 日期标题 | 数字标题 | 忽略字段重出江湖 |
---|---|---|---|
自定义Kusch:字符串0 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串1 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串2 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串3 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串4 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串5 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串6 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串7 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串8 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
自定义Kusch:字符串9 | 2020年09月14日14时41分10秒 | 0.56 | 时而忽略,时而不忽略,你觉得忽略不忽略? |
- 依旧是最先创建实体类对象
import lombok.Data;
@Data
public class DemoReadExcel {
private String stringTitle;
private String date;
private Double doubleData;
private String ignore;
}
- 进行读取操作(包含持久层操作):
读取操作类
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.superman.test.entity.DemoReadExcel;
import com.superman.test.read.DemoDataListener;
public class TestExcelRead {
public static void main(String[] args) {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法1:
String fileName = "E:\\ideaWorkspace\\temp001.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
// EasyExcel.read(fileName, DemoReadExcel.class, new DemoDataListener()).sheet().doRead();
// 写法2:
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoReadExcel.class, new DemoDataListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
}
由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器:DemoDataListener
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.superman.test.entity.DemoReadExcel;
import java.util.ArrayList;
import java.util.List;
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoReadExcel> {
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<DemoReadExcel> list = new ArrayList<>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoReadExcel data, AnalysisContext context) {
System.out.println("解析到一条数据:" + data.toString());
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
System.out.println("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
System.out.println("开始存储数据库!");
demoDAO.save(list);
System.out.println("存储数据库成功!");
}
}
模拟持久层代码
import com.superman.test.entity.DemoReadExcel;
import java.util.List;
/**
* 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
**/
public class DemoDAO {
public void save(List<DemoReadExcel> list) {
// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
System.out.println("DemoDAO存储,模拟List: " + list.toString());
}
}
- 读取Excel的部分特殊使用需求示例:
- 读取指定列的信息,可以使用name或者index方式,例如:
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
- 多行头的指定(主要解决有多行头,可能导致将头读成数据问题。)
import com.alibaba.excel.EasyExcel;
import com.superman.test.entity.DemoReadExcel;
import com.superman.test.read.DemoDataListener;
public class TestExcelRead {
public static void main(String[] args) {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
String fileName = "E:\\ideaWorkspace\\temp001.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
//.headRowNumber(50)指定Excel文件头的行数,默认是1行,这里搏一搏指定个50行头
EasyExcel.read(fileName, DemoReadExcel.class, new DemoDataListener()).sheet().headRowNumber(50).doRead();
}
}
常见api
常见类的作用顺序及功能
-
EasyExcel 入口类,用于构建开始各种操作
-
ExcelReaderBuilder ExcelWriterBuilder 构建出一个 ReadWorkbook WriteWorkbook,可以理解成一个excel对象,一个excel只要构建一个
-
ExcelReaderSheetBuilder ExcelWriterSheetBuilder 构建出一个 ReadSheet WriteSheet对象,可以理解成excel里面的一页,每一页都要构建一个
-
ReadListener 在每一行读取完毕后都会调用ReadListener来处理数据
-
WriteHandler 在每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据
-
所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet
常见注解
注解 | 作用 |
---|---|
ExcelProperty | 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推 |
ExcelIgnore | 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段 |
DateTimeFormat | 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat |
NumberFormat | 用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat |
ExcelIgnoreUnannotated | 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与 |