☰
Jasper报表PDF中文丢失与JSON数据源交叉报表排查
2026/11/2 3:30:02 网站建设 项目流程

简介:《Jasper报表制作小技巧》是一份面向报表开发初学者和需要快速上手Jaspersoft的IT从业者的实操型文档,聚焦报表结构认知与常见制作难点,帮助读者在PDF输出、JSON数据源、分组统计等场景中减少试错。压缩包内仅含1个docx文件,约599KB,全文以操作要点和问题处理为主,不依赖配套工程,适合作为日常报表设计时的速查材料。目前已有242人学习下载,具备一定参考热度。文档先解释Title、Page Header、Column Header、Detail、Column Footer、Page Footer、Summary等Band的显示位置与作用,再整理界面布局与快捷键、JSON数据源接入、中文字段在PDF中的显示处理、手工补字段、明细分组、序号列变量、自定义纸张大小等操作技巧;内容预览还涉及Crosstab交叉报表、字段类型调整、控件按条件显示隐藏,以及报表模板上传配置和SQL条件模板等延伸用法。读者可据此理解报表从取数、排版到预览编译的完整链路,对照处理字段遗漏、分组分页和中文字体等常见问题,提升搭建效率与输出质量。

1. 中文标签在预览里好好的,一上传系统就变空白或方块字

第一次遇到这个问题,我盯着屏幕反复刷新了三遍浏览器——本地预览里“收货商品统计”六个字清清楚楚,同一个模板上传到系统后,PDF 一打开,中文全没了,只剩下数字和英文。这不是个例,Jasper 报表只要走 PDF 导出这条路,中文字体几乎是最常见的故障点:预览靠浏览器字体兜底,PDF 渲染器却要靠模板里写死的pdfFontName和pdfEncoding,两边根本不是一套逻辑。很多做仓储、物流、管理报表的开发者,白天在 IDE 里调得好好的,晚上配完打印配置一上线就翻车,第二天还得挨个字段补属性。

这篇笔记按我自己踩坑的顺序整理:先把中文丢失、字段对不上、分组不换页这几类高频故障的根因讲透,再落到 JSON 数据源、交叉报表、打印配置里那些不填就报错、填错也没提示的位置,最后给一套能快速定位的手段,帮你把这类报表故障的排查时间缩短。

2. 中文变方块/丢失:PDF 字体属性到底该怎么填

中文丢字是 Jasper 报表里最经典的翻车点,症状还分好几种:有的是整段中文消失,有的是中文变成一个个空心方块,有的是预览正常、导出 PDF 变空白,还有的是某个字段单独不显示而其他中文都在。症状不同,根因基本都落在同一个地方——PDF 字体没有绑定成中文字体,或者编码没声明。

2.1 为什么预览能显示、导出 PDF 就丢字

Jasper 报表有两种渲染路径。屏幕预览走的是 Swing/HTML 渲染,用的是运行环境里已有的字体,系统里装了中文字体,自然能显示。PDF 导出则走 iText 渲染,PDF 文件需要把字体子集嵌入进去,如果模板里没有显式指定pdfFontName,iText 会退回默认的 Helvetica 或 Times,这两个字体根本不包含 CJK 字形,中文就被吞了,或者渲染成一个空方框。

更隐蔽的一种情况:字体指定了、编码没指定。比如只写了pdfFontName="STSong-Light",但没有pdfEncoding="UniGB-UCS2-H",iText 在编码字符时找不到对应映射,中文照样出不来。所以这两个属性是成对的,缺一不可。

还有一种“在系统里丢、在 IDE 里不丢”的现象,是因为报表模板上传到系统后,服务端渲染环境可能跟本地不一样——本地是 Windows,服务端是 Linux 容器,字体库存不同。这种情况下即便模板写了字体名,如果服务端没装对应字体,也会回退。常见做法是优先用 iText 内置的STSong-Light(这是 iText 自带的 CJK 字体包,不依赖系统字体库),比依赖服务器装字体稳得多。

判断到底是哪一类,有个很快的办法:先导出一份只有中文静态文本、没有任何数据绑定的模板。如果静态中文也丢,那 100% 是字体属性没配;如果静态文本正常、只有数据字段丢,问题多半在字段级别的样式没继承到全局样式。

2.2 单个中文标签改属性的写法

最直接的做法是对每个包含中文的文本元素单独设置。选中元素,在属性面板里切到Advanced面板,找到PDF Font name、PDF Encoding、PDF Embedded三项:

属性建议值说明
PDF Font nameSTSong-LightiText 内置简体中文字体,不依赖系统
PDF EncodingUniGB-UCS2-H简体中文的 UCS2 编码,必须与字体匹配
PDF Embeddedtrue把字体子集嵌入 PDF,避免目标机器缺字

三个属性必须同时设置,漏一个就可能白干。PDF Embedded很多人会忽略,觉得字体名写了就够了,但如果不嵌入,PDF 在别的机器上打开时依赖阅读器的字体替换,结果一样不可控。

2.3 全局样式一次性解决所有中文

逐个元素改属性,字段一多就是体力活,而且容易漏。我一般直接改模板的 source,在样式段里插一条全局默认样式,让所有没单独指定字体的元素都继承它:

<!-- 全局默认样式:所有未单独指定字体的文本都走中文字体 --> <style name="pdfStyle" isDefault="true" pdfFontName="STSong-Light" pdfEncoding="UniGB-UCS2-H" isPdfEmbedded="true"/> <!-- 局部覆盖示例:表格明细用更小的字号,字体仍走全局 --> <style name="detailStyle" isDefault="false" fontSize="8" pdfFontName="STSong-Light" pdfEncoding="UniGB-UCS2-H" isPdfEmbedded="true"/>

关键参数就三个:pdfFontName指定字体,pdfEncoding指定编码,isPdfEmbedded决定是否嵌入。isDefault="true"是这条样式生效的前提——它让样式成为模板级默认,所有没有显式style引用的元素都会继承。如果某个元素单独设了style="detailStyle",那它就走局部样式,所以局部样式里也要把这三个属性写全,否则那一块又会丢字。

改完 source 后必须重新编译(Compile),只保存不编译的话,系统上传的模板还是旧的.jasper二进制。这一步是很多“我明明改了怎么还不行”的真实原因。

提示:如果服务端渲染用的是较老的 iText 版本,STSong-Light这个字体名在不同版本里可能略有差异,保险做法是先本地导出一次 PDF,用阅读器的“字体属性”看嵌入的是哪个字体,再反向确认模板里写的名字对不对。

3. 字段对不上 / 分组不换页:JSON 数据源与分组设置的坑

JSON 数据源在 Jasper 里用起来轻,但也埋了两个反直觉的坑:一是字段类型默认全按字符串读,二是分组设了却不生效。这两个故障报错都很“安静”——没有异常,只有结果不对,排查成本反而更高。

3.1 漏字段后手工补 fields 的正确姿势

用 JSON 做数据源时,字段是根据样本数据自动推断出来的。如果样本里恰好缺了某条记录中的某个键,这个字段就不会出现在 outline 的fields列表里,后续绑定表达式时要么找不到、要么编译报错。这种情况在明细数据里很常见,比如某批次没有openQty这个键。

解决路径是手工补字段:在左下角报表大纲里右键fields,选Create Field(不同版本菜单名可能是“新建字段”),然后在属性框里改三样东西:

Name : openQty // 必须与 JSON 中的键名完全一致,大小写敏感 Class : java.lang.Integer // 类型要选对,后面交叉表求和依赖它 Description : 未完成数量 // 仅显示用,不影响取数

最容易翻车的是Name。JSON 的键是大小写敏感的,itemCode和itemcode在 Jasper 里是两个字段,名字对不上,预览时那一列就是空的,而且不报错。我一般的习惯是直接从原始 JSON 里复制键名粘贴过来,绝手打。

Class也不能随便选。JSON 解析出来的数字默认常被识别成java.lang.String,如果在交叉表里要对它做Sum汇总,字符串是没法求和的——要么汇总结果直接空,要么抛类型转换异常。凡是参与统计的字段,一律改成java.lang.Integer或java.math.BigDecimal。

3.2 Create Group 之后为什么不生效

分组这个功能,菜单点下去很容易,但“点了 Create Group 却没换页”是高频问题。根因在于分组只是声明了分组依据,页面上的视觉分隔还得靠 band 高度和换页属性来控制。

我平时用的第一种做法,是把分组头压缩掉、直接靠换页来制造分页感:

1. outline 中右键报表名 → Create Group,选中分组字段(如店铺) 2. 选中 group header 区域 → 属性面板 band properties 3. Height 设为 0 4. 勾选 Start New Column、Start New Page 5. 重新 Compile 并预览

Height设为 0 是让分组头不占视觉空间,但 band 本身还在,换页逻辑仍然生效;Start New Page才是真正让每个分组从新页开始的那一下。只设 Height 不勾换页,结果就是所有数据挤在一起,看着像分组没生效。

第二种做法是把columnHeader高度也设为 0,再把原来的列名全部搬到groupHeader里。这样每一页的开头是分组头带着列名,视觉上像“每个店铺一页”的独立小报表,适合快递面单、按门店归档这类场景。代价是列名只出现在分组头,如果某个分组跨页,第二页就没有列名了——这一点在长明细里要留意。

3.3 序号列用 Variable 而不是手写文本

明细里加序号,不少人第一反应是拖一个文本框手打 1、2、3,结果当然是每行都一样。正确做法是用变量配合内置计数:

1. outline 中右键 fx → Create Variable 2. Name: seqNo 3. Variable Class: java.lang.Integer 4. Calculation: Count 5. Reset Type: Report(整份报表连续编号) 或 Reset Type: Page(每页从 1 重新开始) 6. Expression: $F{itemCode} 或其他非空字段 7. Initial Value Expression: 留空即可

把seqNo拖到 detail 区第一列,Reset Type的选择直接决定编号是整份连续还是每页重置。这里最常踩的坑是Expression写成了常量或者空值,变量计数对象不对,结果要么恒为 1,要么整列空白。另外Calculation选Count时统计的是非空值,如果 Expression 引用的字段恰好有空值,序号会跳号——这也是很多人以为“变量坏了”的真实原因。

4. 常见问题与避坑清单:那些不报错只出错的地方

这一类故障的共性是没有异常堆栈,页面照常生成,只是内容不对。下面几条都是我实际撞过或者帮别人定位过的,按“现象 → 原因 → 解决”记下来。

4.1 常见问题一:明细数字能显示,一求和就是空

现象:交叉表或者明细行里的数字都正常,但加了汇总行之后,合计列一片空白,也没有任何报错。

原因:JSON 数据源把数字字段推断成了字符串类型,Sum计算在字符串上得不到结果,Jasper 多数时候静默处理,不抛异常。

解决:回到 outline,选中该字段,把Class改成java.lang.Integer或对应数值类型,然后重新编译。凡是参与Sum、Average的字段,导入时就该检查类型,别等到汇总出错再回头找。

4.2 常见问题二:自定义纸张导出后尺寸被裁

现象:按面单尺寸设了自定义页面,预览边界也对,导出 PDF 后右边和下边被裁掉一截。

原因:Edit Page Format里改了宽高,但Units还是默认单位(inch),填进去的毫米数被当成英寸解析,页面实际巨大,反过来又因为某些区域固定而出现裁切错位。

解决:改尺寸时先把Units切成mm,再填数值。另外面单场景记得把 Title、Page Header、Page Footer 等不用的 band 高度全部设为 0,只保留 Detail,否则那些看不见的 band 会占掉页面高度,把明细挤出去。

4.3 常见问题三:控件该隐藏时不隐藏

现象:想用条件控制某个标记(比如运单上区分陆运/航空的字母)只在特定值时显示,结果要么一直显示,要么永远不显示。

原因:EQUALS()函数的参数顺序和字段类型没对上,或者表达式写在Print When Expression里但返回值不是布尔值。

解决:在元素属性的Print When Expression里写布尔表达式,形如$F{transportType} != null && $F{transportType}.equals("E")。注意Print When Expression的返回值必须是java.lang.Boolean,直接写比较表达式没问题,但如果掺了赋值或字符串拼接,就会被当成非空恒真,控件永远显示。

4.4 常见问题四:上传快成功了,结果报表打不开

现象:模板上传流程走得通,但在系统里点打印报“模板加载失败”或直接空白页。

原因:本地改完 source 没有重新编译,上传的.jasper与.jrxml不一致;或者模板里引用了本地数据源的字段别名,服务端数据源里没有同名字段。

解决:改完 source 一律重新 Compile,上传前本地先用空数据源预览一次;字段名与后端查询输出的别名逐个核对,特别是手工补的字段,Name 必须和后端 SQL 里as出来的别名一字不差。

4.5 常见问题五:分组换页了,但组内统计串到别的组

现象:按店铺分组换页,页脚或汇总里的小计把其他店铺的数值也算进来了。

原因:统计变量放在pageFooter或summary,但Reset Type设成了Report而不是Group,变量跨组累加。

解决:组级统计的变量把Reset Type改成Group,并指定Reset Group为对应分组;组内小计放groupFooter,整表合计放summary,位置和重置策略必须对应,错一处就会串数。

5. 打印配置与交叉报表:上传后报错、列显示不全的定位顺序

前面几类故障在 IDE 里就能暴露,打印配置和交叉报表的问题往往要到系统里才现形,排查链路更长,所以单独拎出来讲。

5.1 管理报表上传后查不出数据

模板上传只是第一步,管理报表真正能不能查,取决于“打印配置”里的条件 JSON 和 SQL 模板是否对齐。配置里那段条件描述,格式是字段描述加类型声明:

[ {"field": "", "id": "h1_basic", "name": "基本信息", "type": "h1"}, {"field": "beg", "id": "beg", "name": "起始日期", "type": "date"}, {"field": "end", "id": "end", "name": "结束日期", "type": "date"}, {"field": "itemCode", "id": "itemCode", "name": "商品编码", "type": "string"} ]

field、id、name、type四个都是必填,field和id尽量保持一致,type决定下一步 SQL 里用什么方式绑定参数。h1那一条是分组标题,不是查询字段,别给它配 SQL 参数。

SQL 模板里对应的条件写法,关键是OR IS NULL这个兜底:

select rh.closedAt, rd.receiptCode, rh.shipFromName, it.code, it.name, rd.totalQty, rd.openQty, (rd.totalQty - rd.openQty) as redQty from RECEIPT_HEADER rh inner join RECEIPT_DETAIL rd on rh.id = rd.receiptId inner join ITEM it on rd.itemCode = it.code where rh.leadingSts = 900 and (rh.code = :code or :code is null) and (rd.itemCode = :itemCode or :itemCode is null) and (rh.closedAt >= :beg or :beg is null) and (rh.closedAt <= :end or :end is null) order by rh.closedAt desc

参数说明::code、:itemCode、:beg、:end与条件 JSON 里的field一一对应;or :param is null的作用是用户不填该条件时不过滤,少了这段,不填日期就查不出任何数据——这是“配置完查询为空”最常见的原因。

5.2 交叉报表建好了,数据列显示不全

交叉表最常见的两个失败点,一个是类型,一个是区域。Crosstab 只能放在Summary区,拖到别的 band 上要么拖不进去,要么运行时报错。建之前先把标题、页眉、页脚这些多余区域的高度清零,只留Title和Summary,否则布局会被挤压。

添加明细字段那一步,向导里有calculation选项,默认常常是Nothing或Count,要统计数量或金额必须手动改成Sum。这一步漏改,结果就是行列都对但每个格子里是计数或是空。

向导完成后还有一个必做动作:选中 Crosstab 里的全部明细字段,把字体调到 6 号并居中。默认字体对交叉表来说偏大,列一多就互相压盖,表现是“列显示不全”——其实不是没渲染,是被前一个单元格盖住了。数据源里参与统计的字段,导入时就要确认是Integer,字符串类型在交叉表里求和会直接空掉。

6. 快速定位这类报表故障:一套可复用的排查顺序

报表故障最耗时间的不是修,是判断“到底哪一层出的问题”——是数据源、模板属性、还是系统配置。我后来固定了一套从外到内的收敛顺序,基本能在几轮之内把范围压到最小。

第一步,先分清预览和导出的差异。同一个模板,浏览器预览正常、PDF 导出异常,直接锁定字体和编码,跳到第 2 章那三个属性;如果预览就不对,问题在数据源或 band 设置,不用去看 PDF 相关配置。

第二步,用最小模板隔离变量。新建一个只有一页、只有几个静态中文文本的模板,导出 PDF。静态中文也丢,就是全局字体问题;静态正常、数据字段丢,就是字段级样式或字段类型问题。这一步能省掉大量猜测。

第三步,对照下面这张表按症状定位:

症状高概率根因首查位置
PDF 中文消失或方块未设 pdfFontName / pdfEncoding全局 style 段
预览有字、系统无字服务端字体库缺失或模板未重编译编译产物与 style
字段列空白不报错字段 Name 与 JSON 键不一致outline 的 fields
汇总列为空字段 Class 是 String字段类型设置
分组不换页Height 未设 0 或未勾 Start New Pagegroup header band
管理报表查询为空SQL 缺 OR :param IS NULL打印配置 SQL 模板
交叉表列被压盖明细字体过大Crosstab 明细样式

第四步,改完必重编译,上传前本地再导一次 PDF 验证。这条看着像废话,但我踩过的“改了没效果”里,一半以上是上传了未重新编译的旧产物。

第五步,把条件 JSON 和 SQL 参数名做一次逐字比对。:beg和beg、itemCode和itemcode,大小写差一个字母就是查不出数据,而且系统不会提示你“参数绑错了”,只会给你一个空结果。

注意:交叉表统计字段和普通明细字段的类型要求不同,前者必须是数值类型,后者只要能正常显示即可。建模板时如果同一个字段两处都要用,优先按交叉表的要求定类型,避免后期回头改。

从那以后我每次改完报表模板,都会强制走一遍“最小模板导出 PDF + 字段名比对 + 重新编译”这三步,哪怕只是改了一个标签文字。报表这东西的坑大多不在语法,而在那些不报错的地方——多花两分钟验证,比上线后被追着问“为什么打印出来是空的”划算得多。希望这套顺序能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询