EasyExcel 导入
简介
背景
在数据处理和分析的过程中,读取和解析 Excel 文件是一个非常常见的需求。在 Java 开发中,有许多库可以帮助我们实现这一功能。其中,阿里巴巴的 EasyExcel 库因其高性能和易用性,备受开发者青睐。本文将详细介绍如何使用 EasyExcel 读取 Excel 文件,并将内容转换为自定义的数据结构 TableInput
。
我们将通过具体的代码示例,演示如何读取 Excel 文件中的数据,并将其转换为自定义对象,方便后续的数据处理和存储。
依赖项
在开始之前,确保您的项目中已经引入了 api-table
和 EasyExcel
的依赖。以下是在 Maven 项目中添加依赖的方式:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>api-table</artifactId>
<version>${api-table.version}</version>
</dependency>
注意:EasyExcel 依赖于 Apache POI,这可能会增加项目的打包体积。在使用时请注意项目的大小和性能。
异步导入
示例代码
以下是一个完整的示例,演示如何使用 EasyExcel 异步读取 Excel 文件,并处理其内容:
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import org.junit.Test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.litongjava.table.model.TableInput;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.ResourceUtil;
import com.litongjava.tio.utils.json.FastJson2Utils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ReadExcelTest {
@Test
public void readExcel() {
// 获取 Excel 文件的 URL
URL resource = ResourceUtil.getResource("example.xlsx");
// 读取 Excel 文件的字节内容
byte[] bytes = FileUtil.readUrlAsBytes(resource);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 定义 Excel 解析监听器
AnalysisEventListener<Map<Integer, Object>> readListener = new AnalysisEventListener<Map<Integer, Object>>() {
private Map<Integer, String> headMap = null;
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据解析完成后的处理逻辑
log.info("所有数据解析完成");
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
// 处理表头数据
String json = FastJson2Utils.toJson(headMap);
log.info("表头数据:{}", json);
this.headMap = headMap;
}
@Override
public void invoke(Map<Integer, Object> data, AnalysisContext context) {
// 处理每一行数据
String json = FastJson2Utils.toJson(data);
log.info("行数据:{}", json);
TableInput ti = TableInput.create();
for (Entry<Integer, Object> e : data.entrySet()) {
String keyName = headMap.get(e.getKey());
Object value = e.getValue();
ti.set(keyName, value);
}
json = FastJson2Utils.toJson(ti);
log.info("转换后的数据:{}", json);
}
};
// 构建 Excel 读取器
ExcelReaderBuilder readBuilder = EasyExcel.read(byteArrayInputStream, readListener);
// 开始读取
readBuilder.doReadAll();
}
}
解析说明
读取 Excel 文件:通过
ResourceUtil.getResource
获取 Excel 文件的 URL,然后使用FileUtil.readUrlAsBytes
读取文件的字节内容。定义解析监听器:实现
AnalysisEventListener
接口,重写invokeHeadMap
、invoke
和doAfterAllAnalysed
方法。invokeHeadMap
:用于处理表头数据,保存表头的映射关系。invoke
:用于处理每一行的数据,将行数据与表头对应,转换为TableInput
对象。doAfterAllAnalysed
:在所有数据解析完成后执行,可以在此进行一些收尾工作。
开始读取:使用
EasyExcel.read
方法创建读取器,并调用doReadAll
方法开始读取所有数据。
示例输出
表头数据示例:
{
"0": "id",
"1": "orders",
"2": "title",
"3": "content",
"4": "category_id",
"5": "summary",
"6": "locale",
"7": "status",
"8": "files",
"9": "remark",
"10": "creator",
"11": "create_time",
"12": "updater",
"13": "update_time",
"14": "deleted",
"15": "tenant_id"
}
行数据示例:
{
"1": "1",
"2": "测试展示视频",
"3": "",
"4": "400537606909092000",
"6": "zh_cn",
"7": "1",
"8": "[{\"uid\":\"1720576961389\",\"size\":3103640,\"name\":\"image.png\",\"id\":\"400560112936251392\",\"type\":\"image/png\",\"url\":\"https://example.com/image.png\",\"status\":\"done\"}]"
}
转换后的 TableInput
数据:
{
"title": "测试展示视频",
"locale": "zh_cn",
"content": "",
"category_id": "400537606909092000",
"files": "[{\"uid\":\"1720576961389\",\"size\":3103640,\"name\":\"image.png\",\"id\":\"400560112936251392\",\"type\":\"image/png\",\"url\":\"https://example.com/image.png\",\"status\":\"done\"}]",
"orders": "1",
"status": "1"
}
通过上述代码和示例,我们成功地将 Excel 文件中的数据读取并转换为自定义的数据结构 TableInput
,为后续的数据处理和存储做好了准备。
同步导入
使用 doReadAllSync
方法同步读取
除了异步读取外,EasyExcel 还提供了同步读取的方法。以下示例展示了如何使用 doReadAllSync
方法同步读取 Excel 数据:
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.ResourceUtil;
import com.litongjava.tio.utils.json.JsonUtils;
public class ReadExcelDoReadAllSyncTest {
@Test
public void doReadAllSync() {
// 获取 Excel 文件的 URL
URL resource = ResourceUtil.getResource("example.xlsx");
// 读取 Excel 文件的字节内容
byte[] bytes = FileUtil.readUrlAsBytes(resource);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 构建 Excel 读取器
ExcelReaderBuilder readBuilder = EasyExcel.read(byteArrayInputStream);
// 同步读取数据
List<Map<String, Object>> tableInputList = readBuilder.doReadAllSync();
System.out.println("读取的数据条数:" + tableInputList.size());
System.out.println("数据内容:" + JsonUtils.toJson(tableInputList));
}
}
注意事项
使用 doReadAllSync
方法读取后,返回的 List<Map>
中,Map
的键是索引(如 "0"
, "1"
, "2"
)的形式,需要在使用前进行处理和转换。
示例输出:
{
"0": "400637631022534660",
"1": "1",
"2": "其他数据"
}
使用自定义解析监听器同步读取
为了更好地处理数据,可以使用自定义的解析监听器同步读取数据,并将其转换为我们需要的格式:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.ResourceUtil;
import com.litongjava.tio.utils.json.JsonUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ReadExcelSyncTest {
@Test
public void readExcelSync() {
// 获取 Excel 文件的 URL
URL resource = ResourceUtil.getResource("example.xlsx");
// 读取 Excel 文件的字节内容
byte[] bytes = FileUtil.readUrlAsBytes(resource);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 读取数据
List<Map<String, Object>> tableInputList = doReadAllSync(byteArrayInputStream);
System.out.println("读取的数据条数:" + tableInputList.size());
System.out.println("数据内容:" + JsonUtils.toJson(tableInputList));
}
private List<Map<String, Object>> doReadAllSync(InputStream inputStream) {
// 存放解析后的所有数据
List<Map<String, Object>> tableInputList = new ArrayList<>();
// 定义 Excel 解析监听器
AnalysisEventListener<Map<Integer, Object>> readListener = new AnalysisEventListener<Map<Integer, Object>>() {
private Map<Integer, String> headMap = null;
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 所有数据解析完成后的处理逻辑
log.info("所有数据解析完成,总共 {} 行数据", tableInputList.size());
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
// 处理表头数据
this.headMap = headMap;
}
@Override
public void invoke(Map<Integer, Object> data, AnalysisContext context) {
// 处理每一行数据并转换为 Map 对象
Map<String, Object> map = new HashMap<>();
for (Map.Entry<Integer, Object> e : data.entrySet()) {
map.put(headMap.get(e.getKey()), e.getValue());
}
tableInputList.add(map);
}
};
// 构建 Excel 读取器
ExcelReaderBuilder readBuilder = EasyExcel.read(inputStream, readListener);
// 开始读取
readBuilder.doReadAll();
return tableInputList;
}
}
通过这种方式,我们可以自定义数据的解析和转换过程,更加灵活地处理 Excel 中的数据。
映射为实体类
原理说明
在实际开发中,我们通常希望将 Excel 中的数据直接映射为 Java 实体类,方便后续的业务处理。EasyExcel 提供了这种功能,我们只需要定义与 Excel 表头对应的实体类,并使用注解或命名规则,EasyExcel 就能自动将 Excel 数据映射到实体类对象中。
实体类定义
首先,定义一个与 Excel 表头对应的实体类,例如学生信息:
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentExcelVo {
@ExcelProperty("name")
private String name;
@ExcelProperty("email")
private String email;
}
@Data
:来自 Lombok,用于自动生成 getter、setter、toString 等方法。@NoArgsConstructor
:生成无参构造函数。@AllArgsConstructor
:生成全参构造函数。@ExcelProperty
不可省略
服务层实现
在服务层,我们编写导入方法,将 Excel 数据读取并转换为实体类列表:
import java.io.ByteArrayInputStream;
import java.util.List;
import com.alibaba.excel.EasyExcel;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.open.chat.model.ExcelImportStudentVo;
public class StudentService {
public RespBodyVo importByAppId(Long appId, byte[] data) {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
List<StudentExcelVo> list = EasyExcel.read(byteArrayInputStream)
.head(ExcelImportStudentVo.class) // 指定映射的实体类
.sheet()
.doReadSync(); // 同步读取
return RespBodyVo.ok(list);
}
}
控制层接口
在控制层,我们定义接口接收上传的文件,并调用服务层的方法:
import com.litongjava.annotation.EnableCORS;
import com.litongjava.annotation.RequestPath;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.open.chat.services.student.StudentService;
import com.litongjava.tio.http.common.UploadFile;
@RequestPath("/api/v1/student")
@EnableCORS
public class ApiV1StudentController {
public RespBodyVo importByAppId(Long appId, UploadFile file) {
return Aop.get(StudentService.class).importByAppId(appId, file.getData());
}
}
@RequestPath
:指定接口路径。@EnableCORS
:开启跨域支持。
运行结果
假设我们上传了一个包含学生姓名和邮箱的 Excel 文件,Excel 文件中的数据如下:
name | |
---|---|
Tong Li | litong@hawaii.edu |
CheChen | chenche@hawaii.edu |
即 Excel 文件中有两行数据,分别是 Tong Li
和 CheChen
的姓名和邮箱。
接口返回的数据如下:
{
"data": [
{
"name": "Tong Li",
"email": "litong@hawaii.edu"
},
{
"name": "CheChen",
"email": "chenche@hawaii.edu"
}
],
"msg": null,
"code": 1,
"ok": true
}
原理解析
实体类映射:EasyExcel 通过读取实体类的字段名称,自动与 Excel 表头进行匹配。如果字段名称与表头名称一致,则会自动映射。
同步读取:使用
doReadSync
方法,EasyExcel 会将读取的数据直接返回为实体类的列表,方便我们直接使用。数据验证和处理:在实际应用中,我们可以在实体类中添加数据校验注解,或者在读取后对数据进行校验和处理,确保数据的正确性。
总结
通过本文的介绍,我们学习了如何使用 EasyExcel 读取 Excel 文件,并将其内容转换为自定义的数据结构或实体类。我们分别演示了异步读取和同步读取的方法,以及如何将 Excel 数据映射为实体类对象。
EasyExcel 的强大之处在于其高性能和易用性,能够简化我们对 Excel 数据的处理。在实际开发中,我们可以根据业务需求,选择合适的读取方式,并对数据进行相应的处理和校验。