前言:用Java做报表导出的功能,基本上都会用到Apache的Poi,Poi的功能很完善,能满足项目的所有需求,但Poi的基础API太多,不容易上手,且需要编写大量代码来构建基础Excel表格;因此,这里推荐一个易上手的Excel导出工具EasyPoi,采用的是注解配置的方法,开发人员只需要在导出的实体类上加上对应的标签,即可轻松编写表头的内容以及样式等。下面就介绍一下简单的EasyPoi导出以及复杂表头的导出。
按规矩先呈上EasyPoi的文档:EasyPoi,如何引入以及各功能的使用说明都在文档里,这里就不赘述了,下面直接上EasyPoi在项目中的应用。
简单报表导出
导入上图这种简单报表,后台的代码很简单,只需要写两个类,一个用来存放导出模板的Template类,另一个则是将得到的List导出的输出类,springmvc中会放在实现层去处理,Template代码如下:
public class TestTemplate {
/**
* 省略Get、Set方法
* 此处字段名与实体类字段名需保持一致
* 字段顺序尽量与导出报表的表头顺序一致,这样可以少写orderNum
*/
@Excel(name = "月份", height = 20, width = 20)
private String monthRecord;
@Excel(name = "楼宇名称", height = 20, width = 20)
private String buildingName;
@Excel(name = "房间号", height = 20, width = 20)
private String detailHouseNumber;
@Excel(name = "电表ID", height = 20, width = 20)
private String eleMeterId;
@Excel(name = "租户姓名", height = 20, width = 20)
private String renterName;
@Excel(name = "租户状态", replace = {"已搬出_0", "正在租住_1", "退租办理中_2", "入住待召测_3"}, height = 20, width = 20)
private Short renterRent;
@Excel(name = "本月已用电量(度)", height = 20, width = 40)
private BigDecimal elePower;
@Excel(name = "本月剩余电量", height = 20, width = 40)
private BigDecimal eleResPrePower;
@Excel(name = "最后抄表时间", format = "yyyy-MM-dd HH:mm:ss", height = 20, width = 40)
private Date lastReportTime;
}
实现层代码:
public void exportTestTemplate(HttpServletResponse response) {
//新建一个模板List用来接收导出的实际数据List
List<TestTemplate> listTemplates = new ArrayList<>();
//查询结果集
List<TestCDO> list = mapper.selectAll();
//自己封装的List转换类,内部用了BeanUtils.copyProperties这个方法,第一个参数是
sourceList,第二个参数是targetList,第三个参数是targeClass
TestBeanUtils.transformList(list , listTemplates, TestTemplate.class);
/**
* EasyPoi提供的导出方法模板
*/
OutputStream out = null;
String sheetName = "Excel文件名";
try {
ExportParams params = new ExportParams(null, sheetName, ExcelType.XSSF);
params.setTitleHeight((short) 20);
Workbook workbook = ExcelExportUtil.exportExcel(params, TestTemplate.class, listTemplates);
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename="
+ new String((sheetName + ".xlsx").getBytes("GBK"), "ISO-8859-1"));
out = response.getOutputStream();
workbook.write(out);
} catch (Exception e) {
logger.error("导出模板Excel,失败", e);
} finally {
try {
if(null != out){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这样一来,只要前端页面点了导出按钮,就可以选择文件的存放位置,然后就可以顺利导出了
复杂表头报表导出
这是项目中遇到的一个需要导出复杂表头的需求,合计上面的那部分是普通的复杂表头,EasyPoi对纵向的单元格合并具有很方便的注解支持,项目中我用到的是@groupname注解,另外还有@ExcelEntity注解可以实现,详情见文档;合计那两行的数据与最上面的表头的数据来源是不一样的,因此,必须先新增这两行数据放到List的头部,项目中我的处理是对查出来的List使用add方法,这样总计的两条数据就插入在了表格的头部,但是会有一个问题,那就是单元格的横向合并,下面会提到解决方法,首先是Template:
public class FeeCountRoomTemplate {
/**
* 省略了Get、Set方法
* groupName相同,name不同,即name是groupName的子元素
* orderNum控制了表头的排序,数字越大,越在后面,默认值是0
*/
@Excel(name = "房源名称", height = 20, width = 20)
private String buildingName;
@Excel(name = "房间号", height = 20, width = 20)
private String detailHouseNumber;
@Excel(name = "日期", height = 20, width = 20)
private String dayRecord;
@Excel(name = "电费", groupName = "收入统计", height = 20, width = 20, orderNum = "0")
private BigDecimal eleChargeAmount;
@Excel(name = "市政水费", groupName = "收入统计", height = 20, width = 20, orderNum = "1")
private BigDecimal municipalWaterChargeAmount;
@Excel(name = "纯净水费", groupName = "收入统计", height = 20, width = 20, orderNum = "2")
private BigDecimal pureWaterChargeAmount;
@Excel(name = "热能费", groupName = "收入统计", height = 20, width = 20, orderNum = "3")
private BigDecimal heatChargeAmount;
@Excel(name = "退款", groupName = "支出统计", height = 20, width = 20)
private BigDecimal reDepositAmount;
@Excel(name = "总计", height = 20, width = 20, orderNum = "4")
private BigDecimal singleTotalAmount;
}
然后是实现层:
public void exportFeeCountByTime(HttpServletResponse response) {
//与简单导出一致
List<FeeCountTimeTemplate> listTemplates = new ArrayList<>();
FeeCountSQO feeCountSQO = new FeeCountSQO();
List<FeeCountCDO> list = feeCountMapper.selectFeeByTimeListAll(); //查询List
FeeCountCDO countCDO = feeCountMapper.selectFeeByTimeTotal(); //汇总数据(只有一条)
//这里的思路是按照表头的格式构造两行数据,在最上方插入,
//这时候你可能会有疑问,合计是3个单元格的合并,但是表头却是3个单元格,
//其实没有关系,我们先插入一个合计在最前面(
//一定要插入在需要合并的单元格的最左边否则合并时数据会消失),
//后面的2个用不到的单元格就插入null,插入总计数据的时候同理
FeeCountCDO copySingleCDO = new FeeCountCDO();
copySingleCDO.setMonthRecord("合计");
copySingleCDO.setBuildingName(null);
copySingleCDO.setDetailHouseNumber(null);
copySingleCDO.setEleChargeAmount(countCDO.getEleChargeAmountSum());
copySingleCDO.setMunicipalWaterChargeAmount(
countCDO.getMunicipalWaterChargeAmountSum());
copySingleCDO.setPureWaterChargeAmount(countCDO.getPureWaterChargeAmountSum());
copySingleCDO.setHeatChargeAmount(countCDO.getHeatChargeAmountSum());
copySingleCDO.setReDepositAmount(countCDO.getReDepositAmountSum());
copySingleCDO.setRoomTotalAmount(countCDO.getRoomsTotalAmount());
FeeCountCDO copyTotalCDO = new FeeCountCDO();
copyTotalCDO.setMonthRecord("合计");
copyTotalCDO.setBuildingName(null);
copyTotalCDO.setDetailHouseNumber(null);
copyTotalCDO.setEleChargeAmount(countCDO.getFeesSum());
copyTotalCDO.setMunicipalWaterChargeAmount(null);
copyTotalCDO.setPureWaterChargeAmount(null);
copyTotalCDO.setHeatChargeAmount(null);
copyTotalCDO.setReDepositAmount(countCDO.getReDepositAmountSum());
copyTotalCDO.setRoomTotalAmount(countCDO.getRoomsTotalAmount());
//插入到结果集的第一个行(注意先插入的实际是第二行)
list.add(0, copySingleCDO);
list.add(0, copyTotalCDO);
IgmsBeanUtils.transformList(list, listTemplates, FeeCountTimeTemplate.class);
OutputStream out = null;
String sheetName = "Excel文件名";
try {
ExportParams params = new ExportParams(null, sheetName, ExcelType.XSSF);
params.setTitleHeight((short) 20);
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename="
+ new String((sheetName + ".xlsx").getBytes("GBK"), "ISO-8859-1"));
out = response.getOutputStream();
workbook.write(out);
} catch (Exception e) {
logger.error("导出模板Excel,失败", e);
} finally {
try {
if(null != out){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
加入了2行自定义的导出数据之后你会发现你的总计数据确实加上去了,但是表格的单元格没有合并,如图:
很明显发现合计与收入统计的数据没有合并单元格,这样肯定是不行的,但是看了很久的文档之后也没有发现文档对这种问题有便捷的解决方法,因此我就在想如何用Apache Poi的方法实现横向单元格合并的问题 ,通过查阅资料发现,Poi有一个Region方法可以实现横向和纵向的任意单元格合并,接着看下去发现它需要对Sheet对象做操作,而得到Sheet又需要Workbook对象,而Workbook对象我们已经得到了,那么问题就可以解决了,下面献上代码:
public void exportFeeCountByTime(HttpServletResponse response) {
/**
* 与之前代码一样,不复制了
*/
try {
/**
* 处理横向单元格合并
*/
ExportParams params = new ExportParams(null, sheetName, ExcelType.XSSF);
params.setTitleHeight((short) 20);
Workbook workbook = ExcelExportUtil.exportExcel(params, FeeCountTimeTemplate.class, listTemplates);
Sheet sheet = workbook.getSheetAt(0);//取此Excel文件的第一个Sheet
//合并第一个‘合计’
//四个参数依次是:起始行,终止行,起始列,终止列
CellRangeAddress craOne = new CellRangeAddress(2, 2, 0, 2);//index从0开始
//合并‘收入统计’
CellRangeAddress craTwo = new CellRangeAddress(2, 2, 3, 6);
//合并第二个‘合计’
CellRangeAddress craThree = new CellRangeAddress(3, 3, 0, 2);
sheet.addMergedRegion(craOne); //第一次合并
sheet.addMergedRegion(craTwo); //第二次合并
sheet.addMergedRegion(craThree); //第三次合并
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition", "attachment; filename="
+ new String((sheetName + ".xlsx").getBytes("GBK"), "ISO-8859-1"));
out = response.getOutputStream();
workbook.write(out);
} catch (Exception e) {
logger.error("导出模板Excel,失败", e);
} finally {
try {
if(null != out){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后你就会发现表格已经按照需求完美导出了
之前还担心自定义的两行的合并单元格之后不会居中结果也自动居中了