使用ApachePOI导出Excel文件

70 阅读6分钟

摘要

本文介绍ApachePOI的作用、缺点、三大工作流,以及如何自定义注解和工具类导出.xlsx文件。

认识ApachePOI

ApachePOI能够处理Excel文件,支持xls和xlsx格式,也可操作doc和docx格式的Word文档读写,还能处理PowerPoint演示文稿等。官方API提供了大量的类和方法,例如操作Excel文件的行列、单元格样式、数据验证等。  

官网poi.apache.org/

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、资讯、面试等多个领域。无论是前沿科技的探索,还是实用技巧的总结,我们都致力于为大家呈现有价值的内容。期待与你共同进步,开启技术之旅。