简介
官网地址:https://easyexcel.opensource.alibaba.com/
项目地址:https://gitee.com/HUGUOAN/easy-excel
快速开始
新建项目
导入依赖
EasyExcel -> QuickStart -> pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hga</groupId>
<artifactId>QuickStart</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
安装插件
File -> settings -> plugins -> Marketplace
开启注解编译
File -> settings -> Build、Execution、Deployment -> Complier -> Annotation Processors
新建项目结构
代码编写
1. 工具类 - FilePathUtil
package com.hga.utils;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
* 获取项目类路径-工具类
*/
public class FilePathUtil {
/**
* 获取项目类路径
* @return 类路径地址
*/
public static String getPath(){
String path = FilePathUtil.class.getClassLoader().getResource("").getPath();
// 设置路径字符集为 UTF-8
path = URLDecoder.decode(path, StandardCharsets.UTF_8);
return path;
}
/**
* 获取项目路径
* @return 项目路径地址
*/
public static String getProjectPath(){
return FilePathUtil.class.getResource("/").getPath().replace("classes/","");
}
/**
* 用于测试输出路径
* @param args
*/
public static void main(String[] args) {
System.out.println(getPath()); // E:/Java/Study/EasyExcel/QuickStart/target/classes/
System.out.println(getProjectPath()); // E:/Java/Study/EasyExcel/QuickStart/target/
}
}
2. 简单写excel
官网案例:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write
- 编写模型类并加入注解
- 编写获取测试数据的方法
- 调用官方API完成写功能
2.1 编写模型类并加入注解
com -> hga -> pojo -> Employee.java
package com.hga.pojo;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 与 excel 文件相对应的模型类
*
* 解释: excel 中每一列 对应于 模型中的每一个属性
*/
@Data // lombok 中的注解 : 帮助我们构建setter、getter、toString、equals、hashcode 方法
@NoArgsConstructor // lombok 中的注解 : 生成无参构造方法
@AllArgsConstructor // lombok 中的注解 : 生成全参数构造方法 顺序为属性编写的顺序
public class Employee {
/*
@ExcelProperty("文字") easyExcel 中的注解: 添加到对应的属性上,对应文字,将是生成的excel的列名
@ExcelIgnore easyExcel 中的注解: 添加到对应的属性上,忽略当前字段,将不会生成对应的excel的列
*/
// 成员变量
@ExcelProperty("员工工号")
private Integer id;
@ExcelProperty("员工姓名")
private String name;
@ExcelProperty("入职日期")
private Date date;
@ExcelProperty("员工工资")
private Double salary;
/**
* 忽略的属性
*/
@ExcelIgnore
private String ignore;
}
2.2 编写获取测试数据的方法
com -> hga -> write -> SimpleWrite.java
package com.hga.write;
import com.alibaba.excel.util.ListUtils;
import com.hga.pojo.Employee;
import java.util.Date;
import java.util.List;
/**
* 快速入门: 写数据
*/
public class SimpleWrite {
/**
* 测试数据
* @param count 条数
* @return 数据集合
*/
private List<Employee> data(int count) {
List<Employee> list = ListUtils.newArrayList(); // alibaba 提供的工具类创建 集合
for (int i = 1; i <= count; i++) {
list.add(new Employee(i, "姓名"+i,new Date(),6.6 * i,""));
}
return list;
}
}
2.3 调用官方API完成写功能
com -> hga -> write -> SimpleWrite.java
官方提供的三种方法:
/**
* 最简单的写
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 直接写即可
*/
@Test
public void simpleWrite() {
// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入
// 写法1 JDK8+
// since: 3.0.0-beta1
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class)
.sheet("模板")
.doWrite(() -> {
// 分页查询数据
return data();
});
// 写法2
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法3
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
}
}
本案例使用第二种:
@Test
public void write(){
// 获取文件路径 并拼接文件名
String fileName = FilePathUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, Employee.class).sheet("sheet-写入的数据").doWrite(data(10));
}
2.4 查看结果
3. 简单读excel
官方案例:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read
- 编写模型类并加入注解
- 编写监听器
- 调用官方API完成读功能
3.1 编写模型类并加入注解
这里我们就读取刚刚写的excel,模型类就是 Employee.java
3.2 编写监听器
监听器:当从excel中读取数据时,就会触发监听器中的对应方法。
3.3 调用官方API完成读功能
com -> hga -> read -> SimpleRead
官方提供的三种方法:
/**
* 最简单的读
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>
* 3. 直接读即可
*/
@Test
public void simpleRead() {
// 写法1:JDK8+ ,不用额外写一个DemoDataListener
// since: 3.0.0-beta1
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
for (DemoData demoData : dataList) {
log.info("读取到一条数据{}", JSON.toJSONString(demoData));
}
})).sheet().doRead();
// 写法2:
// 匿名内部类 不用额外写一个DemoDataListener
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
/**
* 单次缓存的数据量
*/
public static final int BATCH_COUNT = 100;
/**
*临时存储
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(DemoData data, AnalysisContext context) {
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
log.info("存储数据库成功!");
}
}).sheet().doRead();
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法3:
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 写法4
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
}
}
本案例使用第一种:
package com.hga.read;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
/**
* 快速入门: 读数据
*/
@Slf4j
public class SimpleRead {
@Test
public void read(){
String fileName = FilePathUtil.getProjectPath() + "simpleWrite1711275798756.xlsx";
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`监听器的构造函数设置(默认提供的监听器) 可以自己实现
EasyExcel.read(fileName, Employee.class, new PageReadListener<Employee>(dataList -> {
for (Employee data : dataList) {
System.out.println(data.toString());
}
})).sheet().doRead();
}
}
3.4 查看结果
进阶教程
1. 批量写数据
com -> hga -> write -> ManyWrite.java
官方提供的方法:
官方提供
/**
* 重复多次写入
* <p>
* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
* <p>
* 2. 使用{@link ExcelProperty}注解指定复杂的头
* <p>
* 3. 直接调用二次写入即可
*/
@Test
public void repeatedWrite() {
// 方法1: 如果写到同一个sheet
String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
// 这里注意 如果同一个sheet只要创建一次
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
for (int i = 0; i < 5; i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
}
// 方法2: 如果写到不同的sheet 同一个对象
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
}
// 方法3 如果写到不同的sheet 不同的对象
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class
// 实际上可以一直变
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
}
}
本案例使用第二种:
package com.hga.write;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import org.junit.Test;
import java.util.Date;
import java.util.List;
/**
* 批量写数据
*/
public class ManyWrite {
/**
* 测试数据
* @param count 条数
* @return 数据集合
*/
private List<Employee> data(int count) {
List<Employee> list = ListUtils.newArrayList(); // alibaba 提供的工具类创建 集合
for (int i = 1; i <= count; i++) {
list.add(new Employee(i, "姓名"+i,new Date(),6.6 * i,""));
}
return list;
}
@Test
public void write(){
// 方法2: 如果写到不同的sheet 同一个对象
String fileName = FilePathUtil.getProjectPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class).build()) {
// 统计开始写前的时间
long startTime = System.currentTimeMillis();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<Employee> data = data(1000000);
excelWriter.write(data, writeSheet);
}
// 统计结束的时间
long entTime = System.currentTimeMillis();
System.out.println("最后耗时时间:"+ (entTime - startTime) + "毫秒");
}
}
}
2. 模板写数据
写入到excel中的数据格式不是很好看,我们可以提前写好一些样式,形成模板,然后导入数据。
官网案例:官方方法
2.1 编写读取单条数据模板
结果:(这里就不演示代码了)
2.2 编写读取多条数据模板
使用模板写入多条数据代码:
com -> hga -> write -> FillWrite.java
package com.hga.write;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import org.junit.Test;
import java.util.Date;
import java.util.List;
/**
* 填充代码: 使用模板
*/
public class FillWrite {
/**
* 测试数据
* @param count 条数
* @return 数据集合
*/
private List<Employee> data(int count) {
List<Employee> list = ListUtils.newArrayList(); // alibaba 提供的工具类创建 集合
for (int i = 1; i <= count; i++) {
list.add(new Employee(i, "张三"+i,new Date(),6.6 * i,""));
}
return list;
}
@Test
public void write(){
// 方案2 分多次 填充 会使用文件缓存(省内存)
// 获取文件路径 并拼接文件名
String fileName = FilePathUtil.getProjectPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
// 读取模板文件
String templateFileName = FilePathUtil.getProjectPath() + "template.xlsx";
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
excelWriter.fill(data(10), writeSheet);
}
}
}
3. 自定义监听器读数据
官网案例:官网案例
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 假设这个是一个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(DemoData data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
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());
demoDAO.save(cachedDataList);
log.info("存储数据库成功!");
}
}
自定义监听器
定义Dao层,操作数据库操作
3.1 编写Dao层方法
com -> hga -> dao -> EmployeeDao.java
package com.hga.dao;
import com.hga.pojo.Employee;
import java.util.List;
/**
* 模拟操作数据库方法
*/
public class EmployeeDao {
public void save(List<Employee> list){
// 模拟写入到数据中
list.forEach(System.out::println);
}
}
3.2 编写监听器
com -> hga -> listener -> EmployeeListener.java
package com.hga.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.hga.dao.EmployeeDao;
import com.hga.pojo.Employee;
import java.util.ArrayList;
/**
* 监听器: 类似于回调函数,在开始前做什么操作,在完成后做什么操作等。
*/
public class EmployeeListener implements ReadListener<Employee> {
// 定义缓存集合的大小
private int count = 5;
// 定义缓存数据集合
private ArrayList<Employee> list = new ArrayList<>(count);
// 导入dao层接口
private EmployeeDao employeeDao;
public EmployeeListener(EmployeeDao dao){
this.employeeDao = dao;
}
// 每次读取一行数据都会执行监听器中的这个方法
@Override
public void invoke(Employee employee, AnalysisContext analysisContext) {
// 如果每读一条数据,就执行插入数据库中,那么效率必定会慢,所以我们先将读取的数据,写入到缓存集合中,当缓存集合达到指定大小,再写入到数据库中,然后清空缓存集合。
list.add(employee);
// 判断
if(list.size() >= count){
// 写入数据库中,
employeeDao.save(list);
// 清空缓存集合
list.clear();
}
}
// 读取完整个excel之后,执行这个方法
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 可能读取完剩余几条数据没有达到缓存大小,所以在读取完整个sheet页后,再向数据库中写入当前缓存集合中剩余的数据
// 写入数据库中,
employeeDao.save(list);
// 清空缓存集合
list.clear();
}
}
3.3 使用自定义监听器读excel
com -> hga -> read -> MyRead.java
package com.hga.read;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.hga.dao.EmployeeDao;
import com.hga.listener.EmployeeListener;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import org.junit.Test;
/**
* 使用自定义监听器读取数据
*/
public class MyRead {
@Test
public void read(){
String fileName = FilePathUtil.getProjectPath() + "simpleWrite1711275798756.xlsx";
// 构建excel读对象, 使用自定义的EmployeeListener 对象。
ExcelReader reader = EasyExcel.read(fileName, Employee.class, new EmployeeListener(new EmployeeDao())).build();
// 构建easyExcel的sheet页读对象
ReadSheet sheet = EasyExcel.readSheet().build();
reader.read(sheet);
}
}