如何使用JRBeanCollectionDataSource在Jasper Reports中合并表格行单元格?
解决Jasper报表关联单元格合并问题(JRBeanCollectionDataSource实现)
我来帮你搞定这个单元格合并的问题,用JRBeanCollectionDataSource完全可以实现你想要的效果——核心思路是先对数据进行分组处理,再结合Jasper的分组报表配置来实现单元格合并,比双数据集方案更直接高效。
第一步:重构数据模型与分组准备
首先,我们需要把原始数据按关联标识(比如你示例中的"AAA"、"CCC")分组,每个主标识对应一组详情数据。
1.1 创建分组数据实体类
先定义一个用于分组的类,用来存储主标识和对应的详情列表:
public class GroupedData { private String mainName; // 比如"AAA"、"CCC" private List<MyDataClass> details; // 对应主标识的所有详情数据 // Getter和Setter方法 public String getMainName() { return mainName; } public void setMainName(String mainName) { this.mainName = mainName; } public List<MyDataClass> getDetails() { return details; } public void setDetails(List<MyDataClass> details) { this.details = details; } }
1.2 对原始数据进行分组
修改你的Java数据填充代码,把原始的MyDataClass列表按主标识分组,转成GroupedData列表:
List<MyDataClass> datas = new ArrayList<>(); MyDataClass data1 = new MyDataClass("AAA", "AAA details1", "AAA details2", "good", "AAA details3"); MyDataClass data2 = new MyDataClass("AAA", "AAA details11", "AAA details22", "good", "AAA details33"); MyDataClass data3 = new MyDataClass("CCC", "CCC details1", "CCC details2", "good", "AAA details3"); datas.add(data1); datas.add(data2); datas.add(data3); // 按主名称分组(假设MyDataClass有getMainName()方法获取"AAA"这类字段) Map<String, List<MyDataClass>> groupedMap = datas.stream() .collect(Collectors.groupingBy(MyDataClass::getMainName)); // 转换为分组后的数据集 List<GroupedData> groupedDatas = new ArrayList<>(); groupedMap.forEach((key, value) -> { GroupedData groupedData = new GroupedData(); groupedData.setMainName(key); groupedData.setDetails(value); groupedDatas.add(groupedData); }); // 创建主数据源 JRBeanCollectionDataSource mainDataSource = new JRBeanCollectionDataSource(groupedDatas);
第二步:设计Jasper报表模板
接下来在JasperSoft Studio中配置报表,实现单元格合并和详情展示:
2.1 配置主数据集
在报表的Dataset and Query面板中,设置主数据集的数据源类型为JavaBean Data Source,关联GroupedData类,导入它的字段(mainName、details)。
2.2 创建分组
- 右键报表根节点 →
Add Group,选择Group by a field,分组字段选择$F{mainName}。 - 把需要合并的主标识字段(比如
mainName)拖到Group Header band中,设置该单元格的属性:Print Repeated Values:设为false(避免同一组重复打印主标识)Stretch Type:设为Relative to Tallest Object(让单元格高度自动匹配同组详情的总高度,实现合并效果)
2.3 添加详情展示组件
在Detail band中添加一个List组件(推荐)或子报表,用来展示当前分组的详情数据:
- 选中List组件,在
Properties面板中设置Data Source Expression为:new net.sf.jasperreports.engine.data.JRBeanCollectionDataSource($F{details}) - 在List的内部Detail band中,拖入
MyDataClass的字段(比如detail1、detail2等),配置好列格式即可。
第三步:填充报表并输出
最后调整Java代码,传递主数据源并填充报表:
Map<String, Object> parameters = new HashMap<>(); // 加载并编译报表模板 JasperReport jasperReport = JasperCompileManager.compileReport("your_report_template.jrxml"); // 填充报表 JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, mainDataSource); // 输出报表(比如导出为PDF) JasperExportManager.exportReportToPdfFile(jasperPrint, "merged_report.pdf");
为什么之前的方案会有空行?
你之前用printRepeatedValues = false时出现空行,是因为原始数据没有分组,报表的Detail band会为每一条原始数据输出一行,重复的主标识被隐藏后就留下了空行。而分组方案把主标识放在Group Header,详情放在子List里,每一组只会输出一个主标识行,对应所有详情,自然不会有空行。
可选:双数据集方案(如果坚持使用)
如果你一定要用双数据集,也可以这样做:
- 第一个数据集是去重后的主数据(比如只包含AAA、CCC的列表),第二个数据集是完整的详情数据。
- 在报表中,主数据集作为报表数据源,在Detail band中为子报表/List设置
Filter Expression:$F{mainName}.equals($P{CURRENT_MAIN_NAME}),其中CURRENT_MAIN_NAME是主数据集当前行的主标识。 - 这种方式需要额外传递详情数据集作为参数,效率不如分组方案,因为每次都要过滤整个详情数据集。
内容的提问来源于stack exchange,提问作者Sashok
相关产品推荐
相关产品推荐

