Java 怎么高雅的导出 Excel

云栖号资讯:【点击查看更多职业资讯】
在这儿您能够找到不同职业的第一手的上云资讯,还在等什么,快来!

前语

公司项目最近有一个需求:报表导出。整个体系下来,最少超越一百张报表需求导出。这个时分怎样高雅的完结报表导出,开释生产力就显得很重要了。下面首要给咱们共享一下该东西类的运用办法与完结思路。

完结的功用点

关于每个报表都相同的操作,咱们很天然的会抽离出来,这个很简略。而最重要的是:怎样把那些每个报表不相同的操作进行杰出的封装,尽可能的进步复用性;针对以上的准则,首要完结了一下要害功用点:

  • 导出恣意类型的数据
  • 自在设置表头
  • 自在设置字段的导出格局

运用实例

上面说到了本东西类完结了三个功用点,天然在运用的时分设置好这三个要害即可:

  • 设置数据列表
  • 设置表头
  • 设置字段格局

下面的export函数能够直接向客户端回来一个excel数据,其间productInfoPos为待导出的数据列表,ExcelHeaderInfo用来保存表头信息,包含表头称号,表头的首列,尾列,首行,尾行。由于默许导出的数据格局都是字符串型,所以还需求一个Map参数用来指定某个字段的格局化类型(例如数字类型,小数类型、日期类型)。这儿咱们知道个大约怎样运用就好了,下面会对这些参数进行详细解说。

@Override
public void export(HttpServletResponse response, String fileName) {
// 待导出数据
List<TtlProductInfoPo> productInfoPos = this.multiThreadListProduct();
ExcelUtils excelUtils = new ExcelUtils(productInfoPos, getHeaderInfo(), getFormatInfo());
excelUtils.sendHttpResponse(response, fileName, excelUtils.getWorkbook());
}
// 获取表头信息
private List<ExcelHeaderInfo> getHeaderInfo() {
return Arrays.asList(
new ExcelHeaderInfo(1, 1, 0, 0, "id"),
new ExcelHeaderInfo(1, 1, 1, 1, "产品称号"),
new ExcelHeaderInfo(0, 0, 2, 3, "分类"),
new ExcelHeaderInfo(1, 1, 2, 2, "类型ID"),
new ExcelHeaderInfo(1, 1, 3, 3, "分类称号"),
new ExcelHeaderInfo(0, 0, 4, 5, "品牌"),
new ExcelHeaderInfo(1, 1, 4, 4, "品牌ID"),
new ExcelHeaderInfo(1, 1, 5, 5, "品牌称号"),
new ExcelHeaderInfo(0, 0, 6, 7, "商铺"),
new ExcelHeaderInfo(1, 1, 6, 6, "商铺ID"),
new ExcelHeaderInfo(1, 1, 7, 7, "商铺称号"),
new ExcelHeaderInfo(1, 1, 8, 8, "价格"),
new ExcelHeaderInfo(1, 1, 9, 9, "库存"),
new ExcelHeaderInfo(1, 1, 10, 10, "销量"),
new ExcelHeaderInfo(1, 1, 11, 11, "刺进时刻"),
new ExcelHeaderInfo(1, 1, 12, 12, "更新时刻"),
new ExcelHeaderInfo(1, 1, 13, 13, "记载是否现已删去")
);
}
// 获取格局化信息
private Map<String, ExcelFormat> getFormatInfo() {
Map<String, ExcelFormat> format = new HashMap<>();
format.put("id", ExcelFormat.FORMAT_INTEGER);
format.put("categoryId", ExcelFormat.FORMAT_INTEGER);
format.put("branchId", ExcelFormat.FORMAT_INTEGER);
format.put("shopId", ExcelFormat.FORMAT_INTEGER);
format.put("price", ExcelFormat.FORMAT_DOUBLE);
format.put("stock", ExcelFormat.FORMAT_INTEGER);
format.put("salesNum", ExcelFormat.FORMAT_INTEGER);
format.put("isDel", ExcelFormat.FORMAT_INTEGER);
return format;
}

完结作用

Java 怎么高雅的导出 Excel

源码剖析

哈哈,自己剖析自己的代码,有点意思。由于不便利贴出太多的代码,咱们能够先到github上clone源码,再回来阅读文章。✨源码地址✨LZ运用的poi 4.0.1版别的这个东西,想要有用海量数据的导出天然得运用SXSSFWorkbook这个组件。关于poi的详细用法在这儿我就不多说了,这儿首要是给咱们解说怎样对poi进行封装运用。

成员变量

咱们要点看ExcelUtils这个类,这个类是完结导出的中心,先来看一下三个成员变量。

Java 怎么高雅的导出 Excel

list

该成员变量用来保存待导出的数据。

ExcelHeaderInfo

该成员变量首要用来保存表头信息,由于咱们需求界说多个表头信息,所以需求运用一个列表来保存,ExcelHeaderInfo结构函数如下ExcelHeaderInfo(int firstRow, int lastRow, int firstCol, int lastCol, String title)

  • firstRow:该表头所占方位的首行
  • lastRow:该表头所占方位的尾行
  • firstCol:该表头所占方位的首列
  • lastCol:该表头所占方位的尾行
  • title:该表头的称号

ExcelFormat

该参数首要用来格局化字段,咱们需求预先约好好转化成那种格局,不能随用户自己定。所以咱们界说了一个枚举类型的变量,该枚举类只需一个字符串类型成员变量,用来保存想要转化的格局,例如FORMAT_INTEGER便是转化成整型。由于咱们需求承受多个字段的转化格局,所以界说了一个Map类型来接纳,该参数能够省掉(默许格局为字符串)。

public enum ExcelFormat {
FORMAT_INTEGER("INTEGER"),
FORMAT_DOUBLE("DOUBLE"),
FORMAT_PERCENT("PERCENT"),
FORMAT_DATE("DATE");
private String value;
ExcelFormat(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

中心办法

  1. 创立表头

Java 怎么高雅的导出 Excel

    // 创立表头
private void createHeader(Sheet sheet, CellStyle style) {
for (ExcelHeaderInfo excelHeaderInfo : excelHeaderInfos) {
Integer lastRow = excelHeaderInfo.getLastRow();
Integer firstRow = excelHeaderInfo.getFirstRow();
Integer lastCol = excelHeaderInfo.getLastCol();
Integer firstCol = excelHeaderInfo.getFirstCol();
// 行距或许列距大于0才进行单元格交融
if ((lastRow - firstRow) != 0 || (lastCol - firstCol) != 0) {
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
}
// 获取当时表头的首行方位
Row row = sheet.getRow(firstRow);
// 在表头的首行与首列方位创立一个新的单元格
Cell cell = row.createCell(firstCol);
// 赋值单元格
cell.setCellValue(excelHeaderInfo.getTitle());
cell.setCellStyle(style);
sheet.setColumnWidth(firstCol, sheet.getColumnWidth(firstCol) * 17 / 12);
}
}
  1. 转化数据

Java 怎么高雅的导出 Excel

    // 将原始数据转成二维数组
private String[][] transformData() {
int dataSize = this.list.size();
String[][] datas = new String[dataSize][];
// 获取报表的列数
Field[] fields = list.get(0).getClass().getDeclaredFields();
// 获取实体类的字段称号数组
List<String> columnNames = this.getBeanProperty(fields);
for (int i = 0; i < dataSize; i++) {
datas[i] = new String[fields.length];
for (int j = 0; j < fields.length; j++) {
try {
// 赋值
datas[i][j] = BeanUtils.getProperty(list.get(i), columnNames.get(j));
} catch (Exception e) {
LOGGER.error("获取目标特点值失利");
e.printStackTrace();
}
}
}
return datas;
}

这个办法中咱们经过运用反射技能,很奇妙的完结了恣意类型的数据导出(这儿的恣意类型指的是恣意的报表类型,不同的报表,导出的数据必定是不一样的,那么在Java完结中的实体类必定也是不一样的)。要想将一个List转化成相应的二维数组,咱们得知道如下的信息:

  • 二维数组的列数
  • 二维数组的行数
  • 二维数组每个元素的值

假如获取以上三个信息呢?

  • 经过反射中的Field[] getDeclaredFields()这个办法获取实体类的一切字段,然后直接知道一共有多少列
  • List的巨细不便是二维数组的行数了嘛
  • 尽管每个实体类的字段名不一样,那么咱们就真的无法获取到实体类某个字段的值了吗?不是的,你要知道,你具有了反射,你就相当于具有了全世界,那还有什么做不到的呢。这儿咱们没有直接运用反射,而是运用了一个叫做BeanUtils的东西,该东西能够很便利的协助咱们对一个实体类进行字段的赋值与字段值的获取。很简略,经过BeanUtils.getProperty(list.get(i), columnNames.get(j))这一行代码,咱们就获取了实体list.get(i)中称号为columnNames.get(j)这个字段的值。list.get(i)当然是咱们遍历原始数据的实体类,而columnNames列表则是一个实体类一切字段名的数组,也是经过反射的办法获取到的,详细完结能够参阅LZ的源代码。
  1. 赋值正文

Java 怎么高雅的导出 Excel

  // 创立正文
private void createContent(Row row, CellStyle style, String[][] content, int i, Field[] fields) {
List<String> columnNames = getBeanProperty(fields);
for (int j = 0; j < columnNames.size(); j++) {
if (formatInfo == null) {
row.createCell(j).setCellValue(content[i][j]);
continue;
}
if (formatInfo.containsKey(columnNames.get(j))) {
switch (formatInfo.get(columnNames.get(j)).getValue()) {
case "DOUBLE":
row.createCell(j).setCellValue(Double.parseDouble(content[i][j]));
break;
case "INTEGER":
row.createCell(j).setCellValue(Integer.parseInt(content[i][j]));
break;
case "PERCENT":
style.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00%"));
Cell cell = row.createCell(j);
cell.setCellStyle(style);
cell.setCellValue(Double.parseDouble(content[i][j]));
break;
case "DATE":
row.createCell(j).setCellValue(this.parseDate(content[i][j]));
}
} else {
row.createCell(j).setCellValue(content[i][j]);
}
}
}

导出东西类的中心办法就差不多说完了,下面说一下关于多线程查询的问题。

多扯两点

  1. 线程查询数据

Java 怎么高雅的导出 Excel

下面先说说详细思路:由于多个线程之间是一起履行的,你不能够确保哪个线程先履行完毕,可是咱们却得确保数据次序的一致性。在这儿咱们运用了Callable接口,经过完结Callable接口的线程能够具有回来值,咱们获取到一切子线程的查询成果,然后兼并到一个成果会集即可。那么怎样确保兼并的次序呢?咱们先创立了一个FutureTask类型的List,该FutureTask的类型便是回来的成果集。

Java 怎么高雅的导出 Excel

接下来,便是次序塞值了,咱们按次序从tasks列表中取出FutureTask,然后履行FutureTask的get()办法,该办法会堵塞调用它的线程,知道拿到回来成果。这样一套循环下来,就完结了一切数据的按次序存储

    for (FutureTask<List<TtlProductInfoPo>> task : tasks) {
try {
productInfoPos.addAll(task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
  1. 怎样处理接口超时

假如需求导出海量数据,可能会存在一个问题:接口超时,首要原因便是整个导出进程的时刻太长了。其实也很好处理,接口的呼应时刻太长,咱们缩短呼应时刻不就能够了嘛。咱们运用异步编程处理方案,异步编程的完结办法有许多,这儿咱们运用最简略的spring中的Async注解,加上了这个注解的办法能够立马回来呼应成果。关于注解的运用办法,咱们能够自己查阅一下,下面讲一下要害的完结过程:

  • 编写异步接口,该接口担任接纳客户端的导出恳求,然后开端履行导出(留意:这儿的导出不是直接向客户端回来,而是下载到服务器本地),只需下达了导出指令,就能够立刻给客户端回来一个该excel文件的仅有标志(用于今后查找该文件),接口完毕。
  • 编写excel状况接口,客户端拿到excel文件的仅有标志之后,开端每秒轮询调用该接口查看excel文件的导出状况
  • 编写从服务器本地回来excel文件接口,假如客户端查看到excel现已成功下载到到服务器本地,这个时分就能够恳求该接口直接下载文件了。

这样就能够处理接口超时的问题了。

源码地址

https://github.com/dearKundy/excel-utils

源码服用姿态

1.建表(数据自己刺进哦)

CREATE TABLE `ttl_product_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记载仅有标识',
`product_name` varchar(50) NOT NULL COMMENT '产品称号',
`category_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '类型ID',
`category_name` varchar(50) NOT NULL COMMENT '冗余分类称号-防止跨表join',
`branch_id` bigint(20) NOT NULL COMMENT '品牌ID',
`branch_name` varchar(50) NOT NULL COMMENT '冗余品牌称号-防止跨表join',
`shop_id` bigint(20) NOT NULL COMMENT '产品ID',
`shop_name` varchar(50) NOT NULL COMMENT '冗余商铺称号-防止跨表join',
`price` decimal(10,2) NOT NULL COMMENT '产品当时价格-归于热门数据,并且价格改变需求记载,需求价格概况表',
`stock` int(11) NOT NULL COMMENT '库存-热门数据',
`sales_num` int(11) NOT NULL COMMENT '销量',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '刺进时刻',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时刻',
`is_del` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '记载是否现已删去',
PRIMARY KEY (`id`),
KEY `idx_shop_category_salesnum` (`shop_id`,`category_id`,`sales_num`),
KEY `idx_category_branch_price` (`category_id`,`branch_id`,`price`),
KEY `idx_productname` (`product_name`)
) ENGINE=InnoDB AUTO_INCREMENT=15000001 DEFAULT CHARSET=utf8 COMMENT='产品信息表';

运转程序

在浏览器的地址栏输入:http://localhost:8080/api/excelUtils/export即可完结下载

【云栖号在线讲堂】每天都有产品技能专家共享!
课程地址:https://yqh.aliyun.com/zhibo

当即参加社群,与专家面对面,及时了解课程最新动态!
【云栖号在线讲堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时刻:2020-05-09
本文作者: 你在我家门口
本文来自:“互联网架构师 微信大众号”,了解相关信息能够重视“互联网架构师”