java通过EasyExcel实现导入导出(导入、模板导出、和不需要模板的导出)
此文章只是涉及到简单的导入导出
- 通过实体模板导入数据
- 无实体模板导入数据
- 导出数据
- 通过模板导出数据
使用到的maven依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.0</version>
</dependency>
用到的实体和工具类
package com.example.entity;
import cn.hutool.core.date.DatePattern;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.Data;
import java.util.Date;
/**
* @Author user
* @Date 2023/5/6 12:03
* @Description
*/
@Data
public class User {
@ExcelProperty(value = "姓名",index = 0)
private String name;
@ExcelProperty(value = "年龄",index = 1)
private Integer age;
@ExcelProperty(value = "性别",index = 2)
private String sex;
@ExcelProperty(value = "生日",index = 3)
@DateTimeFormat(value = DatePattern.NORM_DATE_PATTERN)
private Date birthday;
@ExcelProperty(value = "备注",index = 4)
private String remark;
}
package com.example.util;
import com.alibaba.excel.annotation.ExcelProperty;
import org.apache.poi.ss.usermodel.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Author user
* @Date 2023/5/6 12:38
* @Description
*/
public class ExcelUtil {
/**
* 手写校验表头
* @param is excel文件流
* @param clazz 模板类
*/
public static void checkExcelHeaders(InputStream is, Class clazz) {
List<String> inputList = new ArrayList<>();
List<String> requiredList = new ArrayList<>();
Field[] allFields = clazz.getDeclaredFields();
for (int i = 0; i < allFields.length; ++i) {
Field field = allFields[i];
ExcelProperty attr = field.getAnnotation(ExcelProperty.class);
if (null != attr) {
//excel里面加入多余的字段
requiredList.add(attr.value()[0]);
}
}
try {
Workbook wb = WorkbookFactory.create(is);
Sheet sheet = wb.getSheetAt(0);
Row row = sheet.getRow(0);
for (Iterator<Cell> iter = row.cellIterator(); iter.hasNext(); ) {
Cell cell = iter.next();
inputList.add(cell.getStringCellValue());
}
} catch (IOException e) {
e.printStackTrace();
}
if (requiredList.stream().anyMatch(e -> !inputList.contains(e))
|| inputList.stream().anyMatch(e -> !requiredList.contains(e))) {
throw new RuntimeException("Excel表头不一致");
}
}
/**
* 设置返回的头部信息
* @param response 返回流信息
* @param fileName 文件名称
*/
public static void setResponse(HttpServletResponse response, String fileName){
//设置头
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String excelFileName = null;
try {
excelFileName = URLEncoder.encode(fileName, String.valueOf(StandardCharsets.UTF_8)).replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
/**
* attachment这个代表要下载的,如果去掉就直接打开了(attachment-作为附件下载,inline-在线打开)
* excelFileName是文件名,另存为或者下载时,为默认的文件名
*/
response.setHeader("Content-disposition", "attachment;filename=" + excelFileName + ".xlsx");
response.setHeader("Content-Type", "application/octet-stream;charset=utf-8");
}
}
导入(读)数据的几个方式
- 无实体模板都数据
// 无模板读取数据,此处read的重载方法很多,这里用到的是输入流的方式
List<Object> list = EasyExcel.read(file.getInputStream()).sheet(0).doReadSync();
- 通过实体模板的方式导入数据
// 有模板读取数据无监听器
List<Object> doReadSync = EasyExcel.read(file.getInputStream(), User.class, null).sheet(0).doReadSync();
读取到对应的数据后通过序列化的方式转换为我们所使用到的对象列表
/通过序列化转换成对应的数据
List<User> users = JSONUtil.toList(JSONUtil.toJsonStr(doReadSync), User.class);
- 通过监听器的方式读取数据
//通过监听器读取文件
/**
* headRowNumber:当读数据时, 0 -这个表没有头,因为第一行是数据 1 -该表有一个行头,这是默认的 2 -这张表有两行头,因为第三行是数据
*/
EasyExcel.read(file.getInputStream(), User.class, new UserListener()).headRowNumber(1).sheet().doRead();
监听器的类
package com.example.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.entity.User;
import java.util.HashMap;
import java.util.Map;
/**
* 这里监听器不要托管Spring
* @Author user
* @Date 2023/5/6 14:42
* @Description
*/
public class UserListener extends AnalysisEventListener<User> {
Map<Integer, String> headMap = new HashMap<>();
Map<Integer, User> userMap = new HashMap<>();
public UserListener() {
}
/**
* 这里可以获取到Excel对应的表头
* @param headMap 表头
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
}
/**
* 这里会把解析到的每一行的数据在这里处理
* 我这里用来获取了行号, 把每一行的数据放进map中
* @param data 行数据
* @param context
*/
@Override
public void invoke(User data, AnalysisContext context) {
//获取当前行 || 这里也可以进行校验数据的空属性等问题
Integer rowIndex = context.readRowHolder().getRowIndex() + 1;
// 这里的话不需要行号的话可以放进list列表中
userMap.put(rowIndex, data);
}
/**
* 这里是把所有的数据解析完成后做的最后处理,我这里是用来校验入库。
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
//校验表头
checkHead();
/**
* 校验是否符合入库标准
*/
// 保存入库
}
public void checkHead(){
String[] headList = {"姓名", "年龄", "性别", "生日", "备注"};
for (int i = 0; i < headList.length; i++) {
if (!headMap.get(i).equals(headList[i])) {
throw new RuntimeException("Excel模板不匹配, 请核对模板表头", null);
}
}
}
}
推荐官网的监听器(可以进行分批读取和保存数据)监听器读数据 更专业清晰
导出数据的几个方式
- 无模板导出 (很简单)
实体类
package com.example.entity;
import cn.hutool.core.date.DatePattern;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.Data;
import java.util.Date;
/**
* @Author user
* @Date 2023/5/6 12:03
* @Description
*/
@Data
public class User {
@ExcelProperty(value = "姓名",index = 0)
private String name;
@ExcelProperty(value = "年龄",index = 1)
private Integer age;
@ExcelProperty(value = "性别",index = 2)
private String sex;
@ExcelProperty(value = "生日",index = 3)
@DateTimeFormat(value = DatePattern.NORM_DATE_PATTERN)
private Date birthday;
@ExcelProperty(value = "备注",index = 4)
private String remark;
}
List<User> userList = new ArrayList<>();
//这里是初始化的数据,实际业务中是从数据库中查询出来的数据
DataList.initializationUser(userList);
ExcelUtil.setResponse(response, "测试用户数据导出");
try {
EasyExcelFactory.write(response.getOutputStream(), User.class).sheet("用户信息").doWrite(userList);
} catch (IOException e) {
e.printStackTrace();
}
复杂的导出请参考官网
- 有模板导出
用到的实体
TotalTiWen
package com.example.entity.tiwen;
import lombok.Data;
/**
* @Author user
* @Date 2023/5/6 13:40
* @Description
*/
@Data
public class TotalTiWen {
private String a;
private String b;
private String c;
private String d;
private String e;
private String f;
private String g;
private String h;
}
TiWen
package com.example.entity.tiwen;
import lombok.Data;
/**
* @Author user
* @Date 2023/5/6 13:40
* @Description
*/
@Data
public class TiWen {
private String j;
private String k;
private String l;
private String m;
}
有模板导出步骤
- 获取模板 (自定义的模板)
- 查询数据(从数据库查询的数据)
- 写入Excel数据
第一步: 准备模板(读取模板文件) - 模板的格式 (注意列表数据是带点的)
//我把模板文件放到了resource下面了,所以读取resource下的模板文件 && 这里也可以是从服务器获取到的文件
InputStream inputStream = ResourceUtil.getResourceObj("templates/tiwen.xlsx").getStream();
第二步:查询数据(我这里是初始化的数据)正常业务中是从数据库中查询出来的数据
//处理数据
TotalTiWen tiWen = new TotalTiWen();
List<TiWen> tiWenList = new ArrayList<>();
//TODO 初始化数据,这里的数据,实际是从数据库中查询出来的
DataList.initializationTiWen(tiWen, tiWenList);
public static void initializationTiWen(TotalTiWen totalTiWen, List<TiWen> tiWens){
totalTiWen.setA("王小海");
totalTiWen.setB("海克斯科技");
totalTiWen.setC("15617223895");
totalTiWen.setD("000023");
totalTiWen.setE("20");
totalTiWen.setF("MMOO99");
totalTiWen.setG("34");
totalTiWen.setH("测试机器");
TiWen t = new TiWen();
t.setJ("1");
t.setK("2022-03-19");
t.setL("103");
t.setM("OOMM9923321312");
TiWen t1 = new TiWen();
t1.setJ("2");
t1.setK("2022-06-19");
t1.setL("552");
t1.setM("OOMM99232312");
TiWen t2 = new TiWen();
t2.setJ("3");
t2.setK("2022-10-30");
t2.setL("423");
t2.setM("OOMM99231123");
TiWen t3 = new TiWen();
t3.setJ("4");
t3.setK("2022-12-24");
t3.setL("674");
t3.setM("OOMM99233");
TiWen t4 = new TiWen();
t4.setJ("5");
t4.setK("2022-11-16");
t4.setL("423423");
t4.setM("OOMM99232");
tiWens.add(t);
tiWens.add(t1);
tiWens.add(t2);
tiWens.add(t3);
tiWens.add(t4);
}
第三步:设置response头部信息(这里是浏览器下载格式)下面用到的工具类在上面
ExcelUtil.setResponse(response, "文件名");
第四步:写入Excel
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
//设置创建行的方式
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).build();
//写入到sheet
WriteSheet oneSheet = EasyExcel.writerSheet(0).build();
//填充
excelWriter.fill(tiWen, oneSheet);
excelWriter.fill(tiWenList, fillConfig, oneSheet);
第五步:关闭对应的流
//关流
excelWriter.finish();
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
- 多sheet导出 (替换上面第四步就行了)
多sheet导出其实和单个sheet一样,只不过是多写入了几次而已
sheet名称就是 打开excel的左下角名称(默认是sheet1 、sheet2、sheet3)
//设置创建行的方式
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLS).build();
//创建第一个sheet 这里是根据sheet的名称写入数据
WriteSheet oneSheet = EasyExcel.writerSheet("EXPORT BILL").build();
/**
* excelDto : 第一个sheet的头部
* goodList :第一个sheet的列表数据
*/
excelWriter.fill(excelDto, oneSheet);
excelWriter.fill(goodList, fillConfig, oneSheet);
//创建第二个sheet 这里是根据sheet的名称写入数据
WriteSheet twoSheet = EasyExcel.writerSheet("INV").build();
/**
* 第二个sheet
* totalExcelDto: 第二个sheet的头部
* informationList:第二个sheet的列表数据
*/
excelWriter.fill(informationList, fillConfig, twoSheet);
excelWriter.fill(totalExcelDto, twoSheet);
//创建第三个sheet 这里是根据sheet的名称写入数据
WriteSheet thirdSheet = EasyExcel.writerSheet("PACKINGLIST").build();
/**
* 第三个sheet
* listExcelDto: 第三个sheet的头部
* infoExcelDtoList:第三个sheet的列表数据
*/
excelWriter.fill(infoExcelDtoList, fillConfig, thirdSheet);
excelWriter.fill(listExcelDto, thirdSheet);
// 下面就是关闭流了,同第五步