摘要
本文介绍ApachePOI的作用、缺点、三大工作流,以及如何自定义注解和工具类导出.xlsx文件。
认识ApachePOI
ApachePOI能够处理Excel文件,支持xls和xlsx格式,也可操作doc和docx格式的Word文档读写,还能处理PowerPoint演示文稿等。官方API提供了大量的类和方法,例如操作Excel文件的行列、单元格样式、数据验证等。
API文档:poi.apache.org/apidocs/5.0…
缺点: 耗内存
三个主要工作流
- HSSF:处理Excel 97-2003版本文件(.xls 格式),一个工作表最多支持65536行、256列,适用于对旧版Excel文件的操作。 HSSFWorkbook workbook = new HSSFWorkbook();
- XSSF:处理Excel 2007及以上版本文件(.xlsx 格式),一个工作表最多支持1048576行、16384列,适用于对新版Excel文件的操作。XSSFWorkbook workbook = new XSSFWorkbook();
- SXSSF:处理Excel 2007及以上版本文件(.xlsx 格式),一个工作表最多支持1048576行、16384列,适用于对大数据量的新版Excel文件的操作,可避免内存溢出。SXSSFWorkbook workbook = new SXSSFWorkbook(500);(500表示内存中保留500行数据,超出部分写入磁盘缓存,但这部分需要及时清理)
导出excel的步骤
1.创建一个Workbook对象(一个新的Excel文件)
2.创建一个或多个Sheet
3.在Sheet中创建行和列
4.填充单元格Cell数据
5.设置单元格的样式,字体、颜色、边框、大小等。
6.将Workbook写入到文件输出流
7.关闭Workbook和输出流,确保数据正确写入文件
8.下载xlsx文件
使用示例
1)引入依赖pom.xml
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.4.0</version> <!-- 安全版本 -->
</dependency>
2)自定义注解
package org.coffeebeans.apachepoi;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <li>ClassName: ExcelField </li>
* <li>Author: OakWang </li>
*/
@Target(ElementType.FIELD)// 注解在字段上
@Retention(RetentionPolicy.RUNTIME)// 运行时保留
public @interface ExcelField {
// 显示的列名
String name() default "";
// 列的宽度,单位为字符
int width() default 15;
// 是否忽略该字段,用于动态控制是否导出
boolean ignore() default false;
// 数字格式,如 "#.##"
String numberFormat() default"";
// 日期格式,如 "yyyy-MM-dd"
String dateFormat() default"";
}
3)映射表头和属性
package org.coffeebeans.apachepoi;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <li>ClassName: UserInfo </li>
* <li>Author: OakWang </li>
*/
@Data
public class UserInfo implements Serializable {
private static final long serialVersionUID= -1470568291792768561L;
@ExcelField(name = "姓名")
private String name;
@ExcelField(name = "年龄")
private Integer age;
@ExcelField(name = "性别")
private String sexStr;
@ExcelField(name = "手机")
private String phone;
@ExcelField(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
@ExcelField(name = "生日", dateFormat = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
private Date brithday;
public UserInfo() {
}
public UserInfo(String name, Integer age, String sexStr, String phone, Date createTime, Date brithday) {
this.name = name;
this.age = age;
this.sexStr = sexStr;
this.phone = phone;
this.createTime = createTime;
this.brithday = brithday;
}
}
4)定义导出工具类
package org.coffeebeans.apachepoi;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* <li>ClassName: ExcelExportUtil </li>
* <li>Author: OakWang </li>
*/
public class ExcelExportUtil {
/**
* 导出excel
*/
public static <T> void exportExcel(HttpServletResponse response, List<T> data, String sheetName, String fileName, Class<T> clazz)throws IOException, IllegalAccessException {
// 创建工作簿,默认使用 XSSFWorkbook
// Workbook workbook = new XSSFWorkbook();
// 如果是大数据量,可以使用流式工作簿 SXSSFWorkbook,注意设置缓存行数
SXSSFWorkbook workbook = new SXSSFWorkbook(500);
try {
// 创建表单
Sheet sheet = workbook.createSheet(sheetName);
// 冻结首行
sheet.createFreezePane(0, 1);
// 创建表头样式
CellStyle headerStyle = createHeaderStyle(workbook);
// 创建数据行样式
CellStyle dataStyle = createDataStyle(workbook);
// 获取所有需要导出的字段
Field[] fields = clazz.getDeclaredFields();
List<Field> exportFields = new ArrayList<>();
for (Field field : fields) {
if (field.isAnnotationPresent(ExcelField.class)) {
ExcelField excelField = field.getAnnotation(ExcelField.class);
if (!excelField.ignore()) {
exportFields.add(field);
}
}
}
// 创建标题行
Row headerRow = sheet.createRow(0);
for (int i=0; i < exportFields.size(); i++) {
Field field = exportFields.get(i);
ExcelField excelField = field.getAnnotation(ExcelField.class);
Cell cell = headerRow.createCell(i);
cell.setCellValue(excelField.name());
cell.setCellStyle(headerStyle);
// 设置列宽
sheet.setColumnWidth(i, excelField.width() * 256);
}
// 写入数据行
for (int i=0; i < data.size(); i++) {
T item= data.get(i);
Row dataRow = sheet.createRow(i + 1);
for (int j=0; j < exportFields.size(); j++) {
Field field = exportFields.get(j);
ExcelField excelField = field.getAnnotation(ExcelField.class);
Cell cell = dataRow.createCell(j);
cell.setCellStyle(dataStyle);
// 反射获取字段值
boolean accessible = field.isAccessible();
field.setAccessible(true);
Object value = field.get(item);
field.setAccessible(accessible);
// 格式化数据
if (value != null) {
if (!excelField.numberFormat().isEmpty() && (value instanceof Number)) {
cell.setCellValue(formatNumber(value.toString(), excelField.numberFormat()));
} elseif (!excelField.dateFormat().isEmpty() && (
value instanceof Date || value instanceof LocalDate || value instanceof LocalDateTime)) {
cell.setCellValue(formatDate(value, excelField.dateFormat()));
} else {
cell.setCellValue(value.toString());
}
}
}
}
// 设置响应头
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");
// 写出文件
try (OutputStream out = response.getOutputStream()) {
workbook.write(out);
}
} finally {
// 关闭工作簿
if (workbook instanceof SXSSFWorkbook) {
((SXSSFWorkbook) workbook).dispose(); //必须清除生成的临时文件
}
workbook.close();
}
}
/**
* 创建表头样式
*/
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
// 设置字体样式
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setColor(IndexedColors.WHITE.getIndex());
font.setBold(true);
style.setFont(font);
// 设置边框样式
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置背景颜色
style.setFillForegroundColor(IndexedColors.GREEN.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置自动换行
style.setWrapText(true);
return style;
}
/**
* 创建表头样式
*/
private static CellStyle createDataStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
// 设置字体样式
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 9);
style.setFont(font);
// 设置边框样式
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 格式化数字
*/
private static String formatNumber(String numberStr, String format) {
double number = Double.parseDouble(numberStr);
return String.format(format, number);
}
/**
* 格式化日期
*/
private static String formatDate(Object dateObj, String format) {
String dateStr;
if (dateObj instanceof Date) {
dateStr = new SimpleDateFormat(format).format((Date) dateObj);
} elseif (dateObj instanceof LocalDate) {
dateStr = ((LocalDate) dateObj).format(DateTimeFormatter.ofPattern(format));
} elseif (dateObj instanceof LocalDateTime) {
dateStr = ((LocalDateTime) dateObj).format(DateTimeFormatter.ofPattern(format));
} else {
dateStr = dateObj.toString();
}
return dateStr;
}
}
5)控制层模拟数据导出
package org.coffeebeans.apachepoi;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
* <li>ClassName: ExportController </li>
* <li>Author: OakWang </li>
*/
@Slf4j
@RestController
@RequestMapping("/apachepoi")
public class ExportController {
@GetMapping("/export")
public void exportBasCrmPatient(HttpServletResponse response){
try {
List<UserInfo> userInfos = new ArrayList<>(); //模拟数据
DateTime dateTime = DateUtil.parseDateTime("2020-01-01 12:34:56");
userInfos.add(new UserInfo("张三", 18, "男", "13888888888", dateTime, dateTime));
userInfos.add(new UserInfo("李四", 19, "女", "13888888888", dateTime, dateTime));
userInfos.add(new UserInfo("王五", 20, "男", "13888888888", dateTime, dateTime));
userInfos.add(new UserInfo("赵六", 21, "女", "13888888888", dateTime, dateTime));
userInfos.add(new UserInfo("孙七", 22, "男", "13888888888", dateTime, dateTime));
userInfos.add(new UserInfo("周八", 23, "女", "13888888888", dateTime, dateTime));
userInfos.add(new UserInfo("吴九", 24, "男", "13888888888", dateTime, dateTime));
ExcelExportUtil.exportExcel(response, userInfos, "用户表","用户导出", UserInfo.class);
} catch (Exception e) {
log.error("导出用户表失败" + e.getMessage());
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}
}
6)测试效果
总结
以上我们了解了ApachePOI的作用、缺点、三大工作流,以及如何自定义注解导出xlsx文件。这个POI比较耗内存,大数据环境下可利用SXSSFWorkbook磁盘缓存提升导出效率。也可扩展性地使用EasyExcel强化Excel导出的功能。
关注公众号:咖啡Beans
在这里,我们专注于软件技术的交流与成长,分享开发心得与笔记,涵盖编程、AI、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。