JasperReport使用DynamicJasper拼接多表格导出PDF出现空白页怎么解决
我最近一直在尝试通过编码方式使用Jasper在同一份报表中生成多个表格,因此决定复用DynamicJasper库(尽管该库的部分文档有时不够清晰,但整体使用起来十分便捷)。
我需要实现的需求是将多个表格报表拼接在同一份报表(或同一页面)中。
我使用的核心数据源是嵌套列表结构,每个子列表对应一张待填充的独立表格,表格表头也采用类似的动态生成逻辑,相关实现代码如下。
根据DynamicJasper的官方说明,实现该需求的逻辑比较直接:首先通过DynamicReportBuilder声明报表的基础配置(标题、宽度、样式等),随后调用addConcatenatedReport方法即可完成多报表拼接。同时官方要求每个子报表必须对应一个唯一字符串标识,作为数据源(即填充数据)的挂钩,后续需要在参数Map中传入该标识对应的数据。
上述流程看起来没有问题,但最终导出的PDF始终是空白页。
以下是我参照示例的实现代码:
报表初始化逻辑
this.dynamicReport = new Generator().withTitle(this.title).withHeaders(this.headerData).prepare(); Map generatedParams = this.buildParams(); this.jasperReport = DynamicJasperHelper.generateJasperReport(this.dynamicReport, new ClassicLayoutManager(), generatedParams); this.jasperPrint = JasperFillManager.fillReport(this.jasperReport, generatedParams);
我自定义了Generator类,用于生成报表结构以及拼接子报表,该类包含两个核心方法:
public DynamicReport prepare() throws Exception { Style rowStyle = new Style(); rowStyle.setBorder(Border.NO_BORDER()); rowStyle.setBackgroundColor(Color.LIGHT_GRAY); rowStyle.setTransparency(ar.com.fdvs.dj.domain.constants.Transparency.OPAQUE); DynamicReportBuilder reportBuilder = new DynamicReportBuilder() .setTitleStyle(new Style()) .setTitle(this.title) .setDetailHeight(15) .setLeftMargin(20) .setRightMargin(20) .setTopMargin(20) .setBottomMargin(20) .setPrintBackgroundOnOddRows(true) .setOddRowBackgroundStyle(rowStyle); for (HeaderData headerData : headerData) { reportBuilder.addConcatenatedReport( this.createSubreportForEntry(headerData), new ClassicLayoutManager(), headerData.getDataSourceName(), // 数据源参数名 DJConstants.DATA_SOURCE_ORIGIN_PARAMETER, DJConstants.DATA_SOURCE_TYPE_COLLECTION, false ); } reportBuilder.setUseFullPageWidth(true); return reportBuilder.build(); } private DynamicReport createSubreportForEntry(HeaderData headerData) throws Exception { FastReportBuilder reportBuilder = new FastReportBuilder(); for (HeaderColumn headerColumn : headerData.getHeaderColumns()) { reportBuilder.addColumn(headerColumn.getColumnName(), headerColumn.getColumnCode(), String.class.getName(), headerColumn.getWidth()); } return reportBuilder .setTitle(headerData.getBoxName()) .setMargins(5, 5, 20, 20) .setUseFullPageWidth(true) .build(); }
自定义表头结构类
public class HeaderData { private String boxName; private String dataSourceName; private List<HeaderColumn> headerColumns; } public class HeaderColumn { private String columnCode; // 数据源映射字段 private String columnName; // 前端展示列名 private int width; private int height; private Style style; private Style headerStyle; }
因为每个表格的列配置都不相同,所以我需要自定义动态表头结构。
参数生成方法
该方法会传入每个子报表对应的数据集:
private Map buildParams() { Map params = new HashMap<>(); this.reportRows.forEach(tuple2 -> { if (tuple2._1 != null && tuple2._2 != null) { params.put(tuple2._1.toString(), tuple2._2); } }); return params; }
生成的参数结构如下:
[ key: DataSourceName1, value: List[Class1(prop1=1, prop2=2), Class1(...), Class1(...), ...], key: DataSourceName2, value: List[Class2(...), Class2(...), Class2(...), ...], key: DataSourceName3, value: List[Class3(...), Class3(...), Class3(...), ...], ]
可以看到每个参数值都是不同类的对象列表,这也是因为不同表格的列配置不同导致的。每个对象的属性名和HeaderColumn.columnCode是一一对应的,确保可以正确映射到表格列。
PDF导出逻辑
JRPdfExporter exporter = new JRPdfExporter(); File outputFile = new File(path); File parentFile = new File(path).getParentFile(); if (parentFile != null) { parentFile.mkdirs(); } FileOutputStream fos = new FileOutputStream(outputFile); SimpleExporterInput simpleExporterInput = new SimpleExporterInput(jasperPrint); OutputStreamExporterOutput simpleOutputStreamExporterOutput = new SimpleOutputStreamExporterOutput(fos); exporter.setExporterInput(simpleExporterInput); exporter.setExporterOutput(simpleOutputStreamExporterOutput); exporter.exportReport();
我不确定是否有纯JasperReport库的实现方案,或者是否可以采用混合方案:比如用DynamicJasper生成报表结构(即上述的Generator类),用原生JasperReport完成数据填充。
注意:我需要纯编码实现,不能使用JRXML文件定义报表。
补充:我调试net.sf.jasperreports.engine.fill.JRVerticalFiller#fillReport方法时发现,代码进入了“无数据”分支,怀疑通过Map传递集合数据源的方式存在问题。
我使用的Jasper版本为6.11.0。
空白页问题是主报表数据源配置缺失导致的,可按以下步骤修改:
- 填充报表时传入空主数据源
你当前调用JasperFillManager.fillReport时没有传入第三个主数据源参数,主报表默认使用空数据源,Jasper检测到主数据源无数据时会直接返回空白页。修改代码如下:
// 传入只有1行的空数据源,触发主报表页面渲染 this.jasperPrint = JasperFillManager.fillReport(this.jasperReport, generatedParams, new JREmptyDataSource(1));
- 配置主报表无数据处理策略
在DynamicReportBuilder配置中添加无数据时的渲染规则,确保即使主报表没有明细数据,也会渲染标题、拼接的子报表等区块:
DynamicReportBuilder reportBuilder = new DynamicReportBuilder() // 原有配置保留 .setWhenNoDataType(WhenNoDataType.ALL_SECTIONS_NO_DETAIL);
- 将集合参数包装为JRDataSource实现类
高版本Jasper要求参数传入的数据源必须是JRDataSource接口实现类,修改buildParams方法,将列表包装为JRBeanCollectionDataSource:
private Map buildParams() { Map params = new HashMap<>(); this.reportRows.forEach(tuple2 -> { if (tuple2._1 != null && tuple2._2 != null) { params.put(tuple2._1.toString(), new JRBeanCollectionDataSource((List) tuple2._2)); } }); return params; }
- 确认依赖版本兼容
Jasper 6.11.0需要搭配DynamicJasper 5.1.2及以上版本使用,低版本DynamicJasper与高版本Jasper API不兼容,也会导致数据填充失败。
内容的提问来源于stack exchange,提问作者Andrea Grimandi

