引言

最近需求中又有excel导出的功能,看到项目里面使用得导出代码写得乱七八糟。根本不想再次复用。本次决定重新写一个工具类,逐步优化,用于后期需求使用。

思路:

  1. 通常导出的数据会是多个实体。
  2. 通过自定义导出注解, 在要导出得实体的字段上标注
  3. 反射取得带有注解的字段, 根据其注解定义的值,设置导出内容
  4. 提供导出方式由两种:
  1. 将生成的excel文件放到服务器,通过读取服务器文件向客户端输出。
  2. 直接将生成的excel写到HttpServletResponse.getOutputStream()输出流中, 然后设置响应头,直接响应客户端。

源代码

依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.17</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.17</version>
    <scope>compile</scope>
</dependency>

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel {

    /**
     * 导出表格 列名
     *
     * @return
     */
    public String name() default "";

}

核心代码

public class ExcelUtil<T> {

    /**
     * 存储行首数据
     */
    private Map<Object, List<String>> topRowCache = new HashMap(6);

    /**
     * 导出数据
     */
    private List<T> data;

    /**
     * 导出实体
     */
    private Class<T> entityClass;

    /**
     * 行首数据
     */
    private List<String> topRow = new ArrayList<>();

    /**
     * 导出到Stream
     */
    private FileOutputStream outputStream;

    /**
     * 导出到文件
     */
    private File outFile;

    /**
     * 文件对象
     */
    private SXSSFWorkbook workbook;

    public ExcelUtil() {

    }

    public ExcelUtil(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    /**
     * 导出数据到指定文件
     *
     * @param outFile 导出文件
     * @param data    导出的数据
     */
    public void exportToFile(File outFile, List<T> data) {
        this.outFile = outFile;
        this.data = data;
        this.init();
        this.dataFill();
    }

    /**
     * 导出数据到指定输出流
     *
     * @param outputStream 导出数据到输出流
     * @param data         导出的数据
     */
    public void exportToStream(FileOutputStream outputStream, List<T> data) {
        this.outputStream = outputStream;
        this.data = data;
        this.init();
        this.dataFill();
    }

    /**
     * 初始化行首数据
     */
    private void init() {
        // 取缓存数据
        if (topRowCache.get(this.entityClass) == null) {
            // 根据对象字段所标注的注解 获取首行数据
            Field[] declaredFields = this.entityClass.getDeclaredFields();
            if (declaredFields != null) {
                for (Field field : declaredFields) {
                    Excel excel = field.getAnnotation(Excel.class);
                    if (excel == null) {
                        continue;
                    }
                    String name = excel.name();
                    this.topRow.add(name);
                }
                topRowCache.put(this.entityClass, this.topRow);
            }
        } else {
            this.topRow = topRowCache.get(this.entityClass);
        }
        // 初始化excel文件 以及 填充首行数据
        SXSSFWorkbook workbook = new SXSSFWorkbook();
        // 创建sheet
        SXSSFSheet sheet = workbook.createSheet("sheet");
        // 创建首行
        SXSSFRow row = sheet.createRow(0);
        // 填充首行数据
        for (int i = 0, len = this.topRow.size(); i < len; i++) {
            SXSSFCell cell = row.createCell(i);
            cell.setCellValue(topRow.get(i));
            // 设置列宽 此处可做优化
            sheet.setColumnWidth(i, 3500);
        }
        this.workbook = workbook;

    }

    /**
     * 数据填充
     */
    private void dataFill() {
        try {
            if (this.data == null || this.data.size() < 1) {
                return;
            }
            // 获取excel的第一个页(sheet)
            SXSSFSheet sheet = this.workbook.getSheetAt(0);
            for (int i = 0, len = this.data.size(); i < len; i++) {
                SXSSFRow row = sheet.createRow(i + 1);
                row.setHeight((short) 450);
                T t = this.data.get(i);
                Field[] declaredFields = t.getClass().getDeclaredFields();
                if (declaredFields == null) {
                    continue;
                }
                // 单元格位置
                int offset = 0;
                // 填充单元格数据
                for (Field field : declaredFields) {
                    Excel excel = field.getAnnotation(Excel.class);
                    // 没有注解的属性不导出
                    if (excel == null) {
                        continue;
                    }
                    // 设置私有属性可访问性
                    field.setAccessible(true);
                    // 获取属性值
                    Object obj = field.get(t);
                    SXSSFCell cell = row.createCell(offset);
                    // 填充
                    cell.setCellValue(obj.toString());
                    offset++;
                }
            }
            if (this.outputStream == null) {
                Assert.notNull(this.outFile, "EXCEL输出 文件路径为空!");
                this.outputStream = new FileOutputStream(this.outFile);
            }
            // 输出
            workbook.write(this.outputStream);
        } catch (IOException | IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

实体类

public class QydjYykhEntity {

    private String id;
    /**
     * 企业主键
     */
    @Excel(name = "企业主键")
    private String pripid;
    /**
     * 公司名称
     */
    @Excel(name = "公司名称")
    private String entname;
    /**
     * 预约网点
     */
    @Excel(name = "预约网点")
    private String bankno;
    /**
     * 办理人电话
     */
    @Excel(name = "办理人电话")
    private String tel;
    /**
     * 办理人
     */
    @Excel(name = "办理人")
    private String name;
    
....省略 get set
}

持久化到磁盘 测试代码

@Test
    public void testExportToFile() {
        ExcelUtil<QydjYykhEntity> comDmEntityExcelUtil = new ExcelUtil(QydjYykhEntity.class);
        List<QydjYykhEntity> data = new ArrayList<>();
        QydjYykhEntity qydjYykhEntity = new QydjYykhEntity();
        qydjYykhEntity.setEntname("测试公司");
        qydjYykhEntity.setTel("18006408888");
        qydjYykhEntity.setBankno("测试网点");
        qydjYykhEntity.setName("张三");
        qydjYykhEntity.setPripid("1");
        qydjYykhEntity.setId("1");
        data.add(qydjYykhEntity);
        data.add(qydjYykhEntity);
        data.add(qydjYykhEntity);
        data.add(qydjYykhEntity);
        comDmEntityExcelUtil.exportToFile(new File("E:\\workspace\\test.xlsx"), data);
    }

持久化到输出流 测试代码

public void downExcelTemplate(HttpServletResponse response) {
        ExcelExportUtil<QydjYykhEntity> comDmEntityExcelUtil = new ExcelExportUtil(QydjYykhEntity.class);
        ServletOutputStream outputStream = null;
        response.setHeader("Content-Disposition", "attachment; filename=template.xlsx");
        try {
            outputStream = response.getOutputStream();
            comDmEntityExcelUtil.exportToStream(outputStream, null);
            outputStream.println();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

导出截图

java easyExcel 注解自动换行 java注解导出excel_List

java easyExcel 注解自动换行 java注解导出excel_List_02

PS:
扩展:
目前项目比较紧张,故上面代码仅仅实现基本的导出功能, 其实可以通过自定义注解扩展很多功能。如:类型转换,动态宽度等。
大数据量:
关于大数据量这一块, 目前我们生产所解决的方案是分批次查询,使用多个sheet来保存。(此种方式在生产上百万的数据量已经能扛住了)
基于当前的方式进行导出, 个人建议如果数据量不大(10w)以下, 可以使用一个excel来保存。
如果数据量大, 可以分批次查询,调用多次exportToStream()方法进行创建多个excel文件, 最后将所有文件打包成一个zip文件。