数据导出组件
组件编码
hzero-starter-export
一、简介
1.1 概述
hzero-starter-export
导出组件基于 apache poi 4.0.0
开发,使用 SXSSF
支持大数据量导出,导出格式为*.xlsx
。在项目中只需依赖该jar包,再结合三个注解即可完成数据的导出。对于导出Excel样式,提供了两种默认实现,同时支持自定义导出样式。
1.2 组件坐标:
<dependency>
<groupId>org.hzero.starter</groupId>
<artifactId>hzero-starter-export</artifactId>
<version>${hzero.starter.version}</version>
</dependency>
1.3 特性
- 支持大数据量导出,不存在内存溢出
- 支持可选择列导出
- 支持头行结构导出、头行打平导出
- 支持自定义Excel导出样式
- 支持自定义单元格格式
- 支持一个DTO中的列分组
- 支持数据自定义渲染
- 使用及其方便简单
二、使用指南
使用该组件导出数据主要会用到三个注解:@ExcelSheet
、@ExcelColumn
、@ExcelExport
。
2.1 @ExcelSheet
在导出的DTO类上,使用@ExcelSheet
标注导出的Sheet,头行结构中,行上也需要使用该注解标注。在@ExcelSheet
中,可配置导出Sheet的标题,分页查询大小等,基本不需配置,使用默认的即可。
/**
* Sheet(Excel)标题 首先根据多语言取,如果多语言为空则取title,title为空则取类名
*/
@AliasFor("zh")
String title() default "";
/**
* 中文标题
*/
@AliasFor("title")
String zh() default "";
/**
* 英文标题
*/
String en() default "";
/**
* 多语言KEY 根据 key & code 获取多语言
* @see #promptCode
* @see #title
*/
String promptKey() default "";
/**
* 多语言CODE 根据 key & code 获取多语言
* @see #promptKey
* @see #title
*/
String promptCode() default "";
/**
* 行偏移量 从第几行开始显示数据 大于等于0
*/
int rowOffset() default 0;
/**
* 占位符,偏移的列可使用占位符显示
*/
String placeholder() default "*****";
/**
* 列偏移量 从第几列开始显示数据 大于等于0
*/
int colOffset() default 0;
/**
* 分页大小 每次查询的数量
*/
int pageSize() default 5000;
2.2 @ExcelColumn
在导出DTO类中,在需要作为导出列的字段上,使用@ExcelColumn
标注,该注解可配置列标题、显示顺序等。
- 在头行结构多Sheet导出中,如果某列需要显示到子Sheet中,可配置
showInChildren=true
; - 如果某个字段是List子列表,需要配置
child=true
。 - 对于一个DTO,想要分组导出不同字段的组合,可设置
groups
属性,通过定义不同的接口来分组,同时需要在@ExcelExport
配置对应的groups
标识。 - 导出的Cell格式可通过
pattern
设置,在BaseConstants.Pattern
里定义了一些基础的日期、数字、金额的格式。 - 如果想要对某个字段自定义显示样式,可设置
renders
属性,需实现ValueRenderer
接口。
/**
* 列标题 首先根据多语言取,如果多语言为空则取title,title为空则取类名
*/
@AliasFor("zh")
String title() default "";
/**
* 中文标题
*/
@AliasFor("title")
String zh() default "";
/**
* 英文标题
*/
String en() default "";
/**
* 多语言KEY 根据 key & code 获取多语言
* @see #promptCode
* @see #title
*/
String promptKey() default "";
/**
* 多语言CODE 根据 key & code 获取多语言
* @see #promptKey
* @see #title
*/
String promptCode() default "";
/**
* 在子列表中显示该列
*/
boolean showInChildren() default false;
/**
* 列顺序
*/
int order() default 1;
/**
* Cell 格式,参考 {@link BaseConstants.Pattern}
*/
String pattern() default "";
/**
* 是否子节点
*/
boolean child() default false;
/**
* 列宽度
*/
String width() default "3000";
/**
* 是否可编辑
*/
boolean editable() default true;
/**
* 分组标识
*/
Class<?>[] groups() default {};
/**
* 数据渲染器,根据需求自行实现渲染器,设置Cell数据时会通过该渲染器来渲染数据和类型。
*
* <pre> example:
* public static class ExampleRenderer implements ValueRenderer {
*
* public Object render(Object value, Object data) {
* ExampleDTO dto = (ExampleDTO) data;
* return "template name = " + dto.name;
* }
* }
* </pre>
*/
Class<? extends ValueRenderer>[] renders() default {};
Example:
@ExcelSheet(zh = "收货记录", en = "Receiving record")
public class ReveRecodeDTO {
@ExcelColumn(zh = "事务编号", en = "trxNum", showInChildren=true)
private String trxNum;
@ExcelColumn(zh = "客户", en = "companyName", groups = {Group2.class})
private String companyName;
@ExcelColumn(zh = "物品编码", en = "itemCode", order = 4, groups = {Group1.class})
private String itemCode;
@ExcelColumn(zh = "物品名称", en = "itemName", order = 3, groups = {Group1.class})
private String itemName;
@ExcelColumn(zh = "日期", en = "trxDate", pattern = BaseConstants.Pattern.DATE)
private Date trxDate;
@ExcelColumn(zh = "数量", en = "quantity", groups = {Group2.class})
private BigDecimal quantity;
@ExcelColumn(zh = "金额", en = "netAmount", pattern = BaseConstants.Pattern.TB_ONE_DECIMAL)
private BigDecimal netAmount;
@ExcelColumn(zh = "原因", en = "moveReason")
private String moveReason;
@ExcelColumn(zh = "接收人", en = "receiptPerson")
private String receiptPerson;
@ExcelColumn(zh = "备注", en = "remark", renders = RemarkValueRenderer.class)
private String remark;
@ExcelColumn(zh = "详情列表", en = "detailsList", child = true)
List<RecordLineDTO> detailsList;
public interface Group1 {}
public interface Group2 {}
public class RemarkValueRenderer implements ValueRenderer {
@Override
public Object render(Object value, Object data) {
RecordLineDTO dto = (RecordLineDTO) data;
return "显示备注:" + dto.remark;
}
}
// getter/setter
}
2.3 @ExcelExport
在导出接口上,使用@ExcelExport
标注,注解需配置导出的DTO。
- 对于只有行的数据,一般可以直接使用已有的List、Page方法即可。
- 接口方法必须有
HttpServletResponse
和ExportParam
参数,HttpServletResponse用于输出Excel,ExportParam用于封装参数。 - 在ExportParam中,通过
exportType=COLUMN
请求导出列;通过exportType=DATA
导出Excel;通过fillerType
指定导出样式; - 对于头行结构,选择了行后,会将行字段名称放入
selection
中,在查询头行数据时,即可按需查询行数据。 - 接口方法如果有
PageRequest
参数,将会分批次查询数据填充到Sheet中,如果没有,则默认一次性查询所有数据。 - 导出时,所有的查询已经控制在一个事务内,不会出现幻读的问题。
/**
* 将该注解加在请求数据的接口上:<br/>
*
* 接口方法必须带有 {@link HttpServletResponse} 参数,将通过 {@link HttpServletResponse#getWriter()} 返回数据 <br/>
* 接口方法必须带有 {@link ExportParam} 参数:
* <ul>
* <li>通过 {@link ExportParam#fillerType} 指定导出方式</li>
* <li>通过 {@link ExportParam#exportType} 指定导出类型</li>
* <ul>
* <li>{@link ExportType#COLUMN} 查询导出的列</li>
* <li>{@link ExportType#DATA} 导出数据</li>
* <li>{@link ExportType#TEMPLATE} 导出模板</li>
* </ul>
* <li>通过 {@link ExportParam#ids} 传入选择导出的列</li>
* </ul>
* 接口方法最好带有分页参数 {@link PageRequest},支持分页查询数据,从而避免大数据量导致内存溢出 <br/>
*
* @author bojiangzhou 2018/07/25
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelExport {
/**
* 导出对象
*/
Class<?> value();
/**
* 分组标识
*/
Class<?>[] groups() default {};
}
Example:
@GetMapping("/export")
@ExcelExport(ReveRecodeDTO.class)
public ResponseEntity export(ReveRecodeDTO record, ExportParam exportParam, HttpServletResponse response, PageRequest pageRequest) {
List<ReveRecodeDTO> list = repository.export(record, exportParam, pageRequest);
return Results.success(list);
}
2.4 导出数据
导出数据需要发起两次请求:
- 第一次请求可导出的列:host/v1/events/export?
exportType=COLUMN
- 第二次传入列ID导出数据:host/v1/events/export?
exportType=DATA
&fillerType=multi-sheet&ids=1&ids=2&ids=3&ids=4&ids=5&ids=6&ids=7……- 单 sheet 页导出:fillerType=single-sheet
- 多 sheet 页导出:fillerType=multi-sheet
三、定制化开发
一般来说,预定义的导出样式可能不满足需求,可以自行开发导出的方式。预定义两种导出方式,单Sheet页导出和多Sheet页导出。
单Sheet页: 头行打平的方式在一个Sheet页中显示
多Sheet页:头行分开Sheet页导出
如果需要自定义导出方式,可实现 ExcelFiller
抽象类(Excel填充器),该抽象类有两个抽象方法:
/**
* @return 填充器类型名称
*/
public abstract String getFillerType();
/**
* 填充数据
*
* @param workbook 工作簿
* @param root 导出列
* @param data 数据
*/
protected abstract void fillData(SXSSFWorkbook workbook, ExportColumn root, List<?> data);
开发完成后,需要将自定义的填充器注册为Spring的bean,这样才能找到该填充器。
四、版本更新日志
0.3.0.RELEASE [2018-10-26]
- 发布
0.3.0.RELEASE
稳定版 - 新增根据 SQL 执行结果导出的方式
0.3.0-SNAPSHOT [2018-10-12]
- 版本跟随 starter 升级至 0.3.0-SNAPSHOT
- 修复已知BUG
0.1.0-SNAPSHOT [2018-08-25]
- 支持大数据量导出
- 支持头行结构导出、头行打平导出
- 预定义两种导出方式,支持自定义导出方式
展望
- 如果数据量过大,又是分页查询,由于使用了事务,可能导致数据库连接占用久,如果同时多个导出,可能导致连接不够用,将在下个版本进行优化。
- 如果导出数据量过大,等待时间会很长,下个版本考虑增加异步的方式,后台生成Excel后,再去下载Excel。
- ….