JasperReports 子报表:把 40 个 band 的巨型模板拆成可维护组件
一个 40-band 的 Jasper 模板什么都做,很快就会变得不可维护。子报表(subreport)让你用小的可复用块组合报表——这是接线方式,以及作者踩过的坑。
为什么用子报表
子报表就是嵌在另一个报表里的已编译报表。需要它的情况:
- 每条记录重复一个明细块(例如发票带行项目);
- 跨报表复用同一组件(页眉、签名块);
- 把一个巨型模板拆成不同人可以各自负责的块。
参数向下传递
父报表通过 <subreportParameter> 把值传给子报表。把父报表的参数当唯一事实来源,转发子报表需要的:
<subreport>
<reportElement x="0" y="0" width="555" height="80"/>
<subreportParameter name="INVOICE_ID">
<subreportParameterExpression>$F{id}</subreportParameterExpression>
</subreportParameter>
<subreportExpression>$P{ITEMS_SUBREPORT}</subreportExpression>
</subreport>
给子报表自己的数据
两种常见喂法:
- 从父报表传数据源(
$P{REPORT_DATA_SOURCE})——父报表已有数据在内存时最佳; - 传连接让子报表跑自己的查询——每个区块对应自己的 SQL 时更干净。
把子报表编译成 .jasper 再作为参数或预加载对象传入:
JasperReport itemsSub = JasperCompileManager.compileReport("items.jrxml");
params.put("ITEMS_SUBREPORT", itemsSub);
踩过的坑
- 每个子报表都要编译。陈旧的
.jrxml/.jasper不匹配是"为什么显示旧数据"的第一大原因; - 盯住宽度。比父 band 宽的子报表会静默溢出或被裁剪,宽度要刻意对齐;
- 设置 Print When Detail Overflows / stretch,让增长的子报表把内容往下推而不是重叠;
- 缓存编译结果。每次请求都编译很贵——启动时编译一次,复用。
性能
报表是读密集的,所以查询优化的规则同样适用:只取要渲染的列、聚合放 SQL、避免在子报表里每行跑一条新查询——一条 join 查询能解决就不要 N 条。
实践建议
- 把报表当代码对待:小而可组合的块、编译产物纳入版本控制、数据抓取尊重数据库;
- 先拆"每条记录重复的明细块",收益最大;共享页眉/签名块次之。
来源:Maintainable Reports with JasperReports Subreports - DEV Community