-
Notifications
You must be signed in to change notification settings - Fork 60
1 导出Excel
EEC目前支持ListSheet
,ListMapSheet
,StatementSheet
,ResultSetSheet
,CSVSheet
和EmptySheet
几种内置的Worksheet,如果不能满足需求你也可以继承已有的Worksheet来扩展,最常见的就是对于大数据量写入时的分片处理,这个在后面会讲到,目前还是从最简单的ListSheet出发。
数据导出应该是开发过程中比较常见的功能,就是这种简单功能如果使用Apache POI来开发可不是一件轻松的活,幸好EEC已经为我们做了大量的封装,使我们可以做到开箱即用,下面代码展示如何开发简单的对象数组导出功能
/**
* 导出学生信息
*/
public void exportStudent(List<Student> students) throws IOException {
new Workbook("二年级学生表") // 新增一个Workbook并指定名称,也就是Excel文件名
.addSheet(new ListSheet<>(students)) // 添加一个Sheet页,并指定导出数据
.writeTo(Paths.get("e:/excel")); // 指定导出位置
}
以上writeTo
方法指定一个输出位置,不需要指定具体文件名称,名称在实例化Workbook时指定,如果未指定则默认使用“新建文件”做为文件名,如果指定到具体文件而不是文件夹则替换原有文件,没有权限则会抛异常。另外writeTo
是终止符,调用该方法将触发写操作,在其后设置的所有属性将不生效。
如果是做web开发则可以将writeTo直接输出到Response的Outputstream中,如下代码
/**
* 直接将excell输出到流
*/
@GetMapping("/download")
public void download(HttpServletResponse response) throws IOException {
String fileName = java.net.URLEncoder.encode("新建文件.xlsx", "UTF-8");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"; filename*=utf-8''" + fileName);
// 查询数据
List<Student> students = studentService.list();
new Workbook().addSheet(new ListSheet<>(students)).writeTo(response.getOutputStream());
}
为了数据安全,EEC默认只会导出标记有@ExcelColumn
的属性,如果Student对象里未标记@ExcelColumn
那上面代码与EmptySheet
等效,如果不方便加入注解你也可以调用forceExport
方法全字段导出(标记了@IgnroeExport
注解除外)或者手动添加Column。
// 手动指定Column
new Workbook().addSheet(new ListSheet<>(Student.randomTestData()
, new Column("学号", "id")
, new Column("姓名", "name")
, new Column("成绩", "score")
))
.writeTo(Paths.get("e:/excel"));
如果其它开发者新增了一些属性且未意识到forceExport标识则会出现数据泄漏风险,为防止数据泄露最好是指定Column导出,这样的话即使对象被人添加了新的敏感字段也不会被自动导出,降低不可预期风险发生。
EEC是通过Workbook#addSheet
方法添加Worksheet,添加的时候你可以指定Sheet的名称,如果不指定则默认使用Sheet {N}
命名。对于导出多个Sheet页只需要多调用几次addSheet方法即可,非常方便。
另外,添加顺序决定导出时各Sheet顺序,如果想调整此顺序可以调用Workbook#insertSheet
方法插入到指定下标(从0开始),与普通的Array操作一样。
下面代码演示生成多个Worksheet
new Workbook("multiSheet")
.addSheet(new ListSheet<>("帐单表", checksTestData()))
.addSheet(new ListSheet<>("客户表", customersTestData()))
.addSheet(new ListSheet<>("用户客户关系表", c2CSTestData()))
.writeTo(Paths.get("e:/excel"));
导出文件如下:
出于某些安全考虑需要隐藏某个或者某些Sheet页该如何处理呢?答案是只需要在对应的Sheet上调用#hidden()
方法。调用该方法后数据依然会正常写出,只是该页被隐藏。
下面代码演示隐藏某个Worksheet
new Workbook("multiSheet")
.addSheet(new ListSheet<>("帐单表", checksTestData()))
.addSheet(new ListSheet<>("客户表", customersTestData()).hidden()) // 隐藏该Sheet
.addSheet(new ListSheet<>("用户客户关系表", c2CSTestData()))
.writeTo(Paths.get("e:/excel"));
导出文件如下,点击右键选择“取消隐藏”就可以还原了:
单个worksheet页有行数上限,xls上限为65,536,xlsx上限为1,048,576,如果数据超过该如何处理呢,需要手动进行截取么,还是抛异常?
EEC是为大数据量而生,所以自然考虑到了这种情况,当数据量超过单sheet上限时会自动进行分页处理,无须用户额外处理,而大多数同类工具均是直接抛异常。
自动分页部分代码解析
/**
* Split worksheet data
*/
@Override
protected void paging() {
// dataSize()是当前一组数据块的大小,limit是获取单个worksheet的行上限
int len = dataSize(), limit = getRowLimit();
// paging
if (len + rows > limit) {
// Reset current index
end = limit - rows + start; // end是标记dataSize的最后位置,因为已经超限了所以当前页只会取未超限的数据
shouldClose = false;
eof = true;
size = limit;
int n = id;
for (int i = end; i < len; ) {
@SuppressWarnings("unchecked")
ListSheet<T> copy = getClass().cast(clone()); // 复制一个新的worksheet
copy.start = i;
copy.end = (i = Math.min(i + limit, len));
copy.size = copy.end - copy.start;
copy.eof = copy.size == limit;
workbook.insertSheet(n++, copy); // 插入到当前worksheet后面
}
// Close on the last copy worksheet
workbook.getSheetAt(n - 1).shouldClose = true; // 如果是最后一个分页则关闭
} else {
end = len;
size += len;
}
}
多行表头请参考 如何设置多行表头
EEC提供Sheet#ignoreHeader
方法来忽略表头输出,当然你在表头上设置的任何信息依然有效,只在输出的时候跳过表头
new Workbook("Ignore header")
.addSheet(new ListSheet<>(randomTestData()).ignoreHeader())
.writeTo(defaultTestPath);
有时候仅仅想导出最简单的数据类型,比如Integer,String,如果定义实体就显得过度设计,此时就可以向下面示例一样设置一个Column且指定类型即
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
new Workbook("Integer array")
.addSheet(new ListSheet<>(list).setColumns(new Column().setClazz(Integer.class)))
.writeTo(defaultTestPath);
EEC内置处理如下类型,并按照文本居左,数字居右,日期/bool/char居中输出
String | CharSequence | int | Integer | short | Short |
byte | Byte | long | Long | float | Float |
double | Double | boolean | Boolean | char | Character |
BigDecimal | java.util.Date | java.sql.Date | java.sql.Timestamp | java.sql.Time | java.time.LocalDate |
java.time.LocalDateTime | java.time.LocalTime |
其余类型均默认调用toString
方法输入,如果需要特殊处理则可以使用自定义ICellValueAndStyle
类并覆写unknownType
方法,示例如下
public class MyXMLCellValueAndStyle extends XMLCellValueAndStyle {
@Override
public void unknownType(int row, Cell cell, Object e, Column hc, Class<?> clazz) {
// 如果认别到自定义枚举则输出枚举desc字段
if (clazz == PlatformEnum.class) {
cell.setSv(((PlatformEnum) e).getDesc());
}
// 其它情况默认处理
else {
super.unknownType(row, cell, e, hc, clazz);
}
}
}
// 添加Worksheet时指定自定认MyXMLCellValueAndStyle即可
new Workbook()
.addSheet(new ListSheet<>(data).setCellValueAndStyle(new MyXMLCellValueAndStyle()))
.writeTo("d:/");
以上代码展示了自定义枚举类型的特殊处理,对于没有权限修改(如对象放在公共的jar包中或者多个团队共同不能修改)的情况下自定义ICellValueAndStyle
就显得特别重要了,当然如果有权限的话你也可以直接在PlatformEnum枚举内添加toString并返回desc属性
unknownType的优先级最低,所以无法在unknownType
方法中处理String等内置类型,如果需要则可以覆写reset
方法,像下面示例一样
public class MyXMLCellValueAndStyle extends XMLCellValueAndStyle {
@Override
public void reset(int row, Cell cell, Object e, Column hc) {
// 调用预处理方法
preCellValue(row, cell, e, hc, hc.getClazz(), hc.processor != null);
if (hc.processor == null) {
cell.xf = getStyleIndex(row, hc, e);
}
}
void preCellValue(int row, Cell cell, Object e, Column hc, Class<?> clazz, boolean hasProcessor) {
// TODO 前置处理内置类型
if (isString(clazz)) {
cell.setSv("##" + e + "##");
return;
}
// 其它类型走原方法
setCellValue(row, cell, e, hc, clazz, hasProcessor);
}
}
查看更多 高级特性
让JAVA操作excel更简单