以下是一个基于EasyExcel封装的Excel工具类,支持高效导出和读取操作:
java">import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
/**
* Excel工具类(基于EasyExcel)
*/
public class ExcelUtil {
/**
* 导出Excel到输出流(单个Sheet)
* @param outputStream 输出流
* @param dataList 数据列表
* @param templateClass 数据模型类
* @param sheetName 工作表名称
* @param <T> 数据类型
*/
public static <T> void exportToStream(OutputStream outputStream,
List<T> dataList,
Class<T> templateClass,
String sheetName) {
ExcelWriter excelWriter = null;
try {
// 设置表头样式
WriteCellStyle headStyle = new WriteCellStyle();
headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 设置正文样式
WriteCellStyle contentStyle = new WriteCellStyle();
contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
// 构建样式策略
HorizontalCellStyleStrategy styleStrategy =
new HorizontalCellStyleStrategy(headStyle, contentStyle);
excelWriter = EasyExcel.write(outputStream, templateClass)
.registerWriteHandler(styleStrategy)
.build();
WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
excelWriter.write(dataList, writeSheet);
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
}
/**
* 导出大数据量Excel(分Sheet写入)
* @param outputStream 输出流
* @param dataSupplier 数据提供函数(分页查询)
* @param templateClass 数据模型类
* @param sheetSize 每个Sheet最大行数
* @param <T> 数据类型
*/
public static <T> void exportBigData(OutputStream outputStream,
PageDataSupplier<T> dataSupplier,
Class<T> templateClass,
int sheetSize) {
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(outputStream, templateClass).build();
int sheetIndex = 1;
int page = 1;
List<T> dataChunk;
while (!(dataChunk = dataSupplier.getPageData(page, sheetSize)).isEmpty()) {
WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + sheetIndex).build();
excelWriter.write(dataChunk, writeSheet);
if (page % (sheetSize / 5000) == 0) { // 每5000行一个Sheet
sheetIndex++;
}
page++;
}
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
}
/**
* 从输入流读取Excel数据
* @param inputStream 输入流
* @param templateClass 数据模型类
* @param dataConsumer 数据消费接口
* @param <T> 数据类型
*/
public static <T> void readFromStream(InputStream inputStream,
Class<T> templateClass,
DataConsumer<T> dataConsumer) {
EasyExcel.read(inputStream, templateClass, new AnalysisEventListener<T>() {
private static final int BATCH_SIZE = 2000;
private List<T> cachedList = new ArrayList<>(BATCH_SIZE);
@Override
public void invoke(T data, AnalysisContext context) {
cachedList.add(data);
if (cachedList.size() >= BATCH_SIZE) {
dataConsumer.consume(cachedList);
cachedList = new ArrayList<>(BATCH_SIZE);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (!cachedList.isEmpty()) {
dataConsumer.consume(cachedList);
}
}
}).sheet().doRead();
}
/**
* 导出Web响应(自动设置Header)
* @param response HttpServletResponse
* @param fileName 文件名(不带后缀)
* @param dataList 数据列表
* @param templateClass 数据模型类
* @param <T> 数据类型
*/
public static <T> void exportWebResponse(HttpServletResponse response,
String fileName,
List<T> dataList,
Class<T> templateClass) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
exportToStream(response.getOutputStream(), dataList, templateClass, "Sheet1");
}
// 自定义转换器注册
public static void registerConverter(Converter<?> converter) {
EasyExcel.registerConverter(converter);
}
/**
* 分页数据提供接口
*/
public interface PageDataSupplier<T> {
List<T> getPageData(int pageNumber, int pageSize);
}
/**
* 数据消费接口
*/
public interface DataConsumer<T> {
void consume(List<T> dataBatch);
}
// 示例数据模型
public static class UserExportModel {
@ExcelProperty("用户ID")
private Long userId;
@ExcelProperty(value = "用户状态", converter = StatusConverter.class)
private Integer status;
// 其他字段...
}
// 示例转换器
public static class StatusConverter implements Converter<Integer> {
private static final Map<Integer, String> STATUS_MAP = Map.of(
1, "正常",
2, "冻结",
3, "注销"
);
@Override
public String convertToExcelData(Integer value, ExcelContentProperty property,
GlobalConfiguration globalConfig) {
return STATUS_MAP.getOrDefault(value, "未知状态");
}
}
}
使用示例
1. 简单导出到文件:
java">try (FileOutputStream fos = new FileOutputStream("export.xlsx")) {
List<User> userList = userService.getAllUsers();
ExcelUtil.exportToStream(fos, userList, User.class, "用户数据");
}
2. Web响应导出:
java">@GetMapping("/export")
public void exportUsers(HttpServletResponse response) throws IOException {
List<User> userList = userService.getAllUsers();
ExcelUtil.exportWebResponse(response, "用户列表", userList, User.class);
}
3. 大数据量分页导出:
java">ExcelUtil.exportBigData(response.getOutputStream(),
(page, size) -> userService.getUsersByPage(page, size),
User.class,
100_0000); // 每个Sheet存储100万数据
4. 读取Excel数据:
java">InputStream inputStream = new FileInputStream("import.xlsx");
ExcelUtil.readFromStream(inputStream, UserImportModel.class, batch -> {
userService.batchProcess(batch);
});
主要特性说明
-
高性能导出:
- 使用分Sheet写入策略,每个Sheet最多存储指定行数
- 自动注册样式策略(表头居中,内容左对齐)
- 支持流式导出,避免内存溢出
-
灵活读取:
- 批量处理机制(默认每2000条处理一次)
- 自动内存管理,适合大文件读取
-
扩展功能:
- 支持自定义转换器(通过
registerConverter
方法) - 支持Web响应自动配置(自动设置Content-Type和文件名)
- 提供分页数据获取接口,适合数据库分页查询
- 支持自定义转换器(通过
-
内存安全:
- 导出时自动分批次写入
- 读取时使用事件驱动模型,不保留完整数据在内存
- 默认开启SXSSF模式(流式写入)
-
样式配置:
- 预定义表头和内容样式
- 可通过覆盖
registerWriteHandler
方法自定义样式
最佳实践建议
-
大文件导出:
- 使用
exportBigData
方法配合分页查询 - 设置合适的Sheet大小(推荐50-100万行/Sheet)
- 添加内存监控逻辑
- 使用
-
数据转换:
- 为枚举字段创建专用转换器
- 对日期字段使用
@ExcelProperty#format
- 复杂转换建议使用数据库联查
-
异常处理:
- 在读取时添加数据校验逻辑
- 使用全局异常处理器捕获
ExcelAnalysisException
-
性能优化:
- 导出时关闭自动列宽计算(
.autoTrim(false)
) - 读取时设置合适的缓存大小(
ReadCache
) - 避免在循环中创建大量临时对象
- 导出时关闭自动列宽计算(