You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JasperReport使用DynamicJasper拼接多表格导出PDF出现空白页怎么解决

问题: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。


解决方案

空白页问题是主报表数据源配置缺失导致的,可按以下步骤修改:

  1. 填充报表时传入空主数据源
    你当前调用JasperFillManager.fillReport时没有传入第三个主数据源参数,主报表默认使用空数据源,Jasper检测到主数据源无数据时会直接返回空白页。修改代码如下:
// 传入只有1行的空数据源,触发主报表页面渲染
this.jasperPrint = JasperFillManager.fillReport(this.jasperReport, generatedParams, new JREmptyDataSource(1));
  1. 配置主报表无数据处理策略
    在DynamicReportBuilder配置中添加无数据时的渲染规则,确保即使主报表没有明细数据,也会渲染标题、拼接的子报表等区块:
DynamicReportBuilder reportBuilder = new DynamicReportBuilder()
        // 原有配置保留
        .setWhenNoDataType(WhenNoDataType.ALL_SECTIONS_NO_DETAIL);
  1. 将集合参数包装为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;
}
  1. 确认依赖版本兼容
    Jasper 6.11.0需要搭配DynamicJasper 5.1.2及以上版本使用,低版本DynamicJasper与高版本Jasper API不兼容,也会导致数据填充失败。

内容的提问来源于stack exchange,提问作者Andrea Grimandi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 14:45:03