
简介这是一份面向使用 Jaspersoft Studio 进行报表开发的技术人员整理的实操技巧文档适合已具备一定报表基础、希望提升制作效率与排错能力的中级开发者参考。内容围绕报表七大 Band 结构展开逐一说明 Title、Page Header、Column Header、Detail、Column Footer、Page Footer 与 Summary 的显示位置和适用场景并补充 Group Header、Group Footer、Last Page Footer 等扩展区域同时覆盖 JSON 数据源接入、字段遗漏时手工补建、报表明细分组、序号列变量创建、自定义纸张尺寸以及 PDF 中文标签乱码的全局样式写法与交叉报表字段类型调整等常见难点。资源包内为 1 个 docx 文档约 599KB便于随时查阅与二次整理。目前已有 242 人浏览学习可作为日常报表开发中的速查手册与问题排查参考。1. Jasper 报表制作小技巧模板在预览里好好的一上传就掉字设计器里预览中文字段清清楚楚点导出 PDF标签变成一片空白再传进系统打开连数字都没了。这是 Jasper 报表制作里最常见的失败情况问题基本不出在拖控件上而出在“设计器看到的”和“系统里跑出来的”不是同一个东西Band 的显示时机没对齐、数据源字段和 SQL 对不上、PDF 字体没做全局配置三件事任意一件没处理都能让一张看着完美的模板在交付当天失效。下面按「结构 → 数据源 → 字体 → 版面 → 排错 → 上线」拆开讲适合已经能画出表格、但一导出和上线就容易出问题的从业者。2. 七个 Band 的版面契约谁只出现一次谁每页都来Jasper 报表的版面并不靠拖坐标堆叠而成它按 Band 的显示规则切分。把某个控件拖进哪个 Band决定了它出现一次、每页出现还是每组出现。搞不清这套规则就会出现“标题只在第一页有”“合计跑到中间页”“列名压在数据行上”这些看起来像 bug 的现象——它们其实都是设计好的行为被误用了。这一章把七个主 Band 加三个分组/收尾 Band 的触发时机、典型用途和设置位置过一遍最后用一张对照表把常见需求映射到具体 Band下次接需求先查表再动手。2.1 一次性 Band 与重复 BandTitle、pageHeader、columnHeader、DetailTitle 段只在整个报表的第一页最上面显示。除第一页外不管报表有多少页Title Band 的内容都不会再出现——它的定位就是“报表标题”不是“页眉”。不少模板把公司名、报表全称、打印日期一股脑塞进 Title结果第二页开始光秃秃误以为是渲染出错其实是按契约干活。判断标准很简单这段内容希望只出现一次还是希望每页都有只出现一次放 Title每页都要放 pageHeader。pageHeader 的显示规则稍微绕一点它在每一页都会出现位置在页面上部第一页时它显示在 Title Band 下面从第二页起它顶到页面最上端。页码、创建时间、创建人、打印人这类公共要素放这里最合适因为它们本来就该每页重复。需要注意的是 pageHeader 的高度会直接挤占 Detail 的可用空间做快递面单这种寸土寸金的版式时这一块要压到 0。columnHeader 放的是列名不是列数据这点原文特意强调过。它在每一页或每个列组顶部重复作用是给下面的 Detail 行做字段标注。把列名直接写成一行 Static Text 扔在 Detail 里第一页看着没问题翻页之后就露馅了。Detail 则是真正的数据区放需要重复出现的内容比如一条销售记录占一行一页能放多少行由 Band 高度和纸张高度共同决定。2.2 收尾 BandcolumnFooter、pageFooter、Summary、lastPageFooter 的分工这四个 Band 都带“统计”属性但统计的范围完全不同混用是最容易出错的地方。columnFooter 是列级别的统计计算值或列说明跟在 Detail 之后、按列对齐适合放“本列合计”“本列均值”这类紧贴数据列的内容。pageFooter 在所在页面的最下端即页脚管的是页级统计比如本页小计、本页行数以及“第 X 页 / 共 Y 页”这种页码信息。Summary 出现在整个报表最后一页的 Detail Band 之后管的是全局合计。举个例子50 条销售记录占用了 3 页需要统计这 50 条的总金额这个值放在 pageFooter 里只会统计到最后一页的数据放在 columnFooter 里可能被列宽切碎正确的落点就是 Summary。很多人把跨页合计写成 Detail 里的一个变量文本翻页就错位根因就是没分清“页级”和“报表级”。lastPageFooter 是最后一页专用的页脚内容会出现在报表最后一页的最后部分。一旦设置了它最后一页的 pageFooter 位置会被它替换掉。典型用途是最后一页要放签收栏、签字盖章区、或者“本报表共 N 页到此结束”的结束语而前面几页仍然用普通页脚。2.3 groupHeader 与 groupFooter分组的边界在哪里报表内容经常需要按某个属性划分显示和计算比如希望以月份为单位每组分开显示销售记录。这时就要定义一个组groupHeader 放组说明或组标志——月份、店铺名、供应商名这类信息groupFooter 放这一组的统计值或说明。组的价值不只是“分块显示”它还是变量重置的边界一个 Sum 变量如果把 resetType 设在组上每进入新组就自动归零重新累加这是手写文本永远做不到的。创建组的入口在报表大纲里右键报表名选 Create Group选中分组字段即可。创建之后设计器会多出对应的 groupHeader 和 groupFooter 两个 Band高度默认不为 0不处理就会在报表里留下两条空白带。后面 2.4 节讲的分页分组做法核心动作就是把这两个 Band 的高度压到 0 或者把内容重新分配。2.4 自定义纸张快递面单只保留 Detail 区的设置方法快递面单、标签、小票这类输出纸张尺寸往往不是 A4。做法是新建完报表初始页面后点击页面最边框外侧的空白部分在 Report 属性区选择 Edit Page Format在 Format 下拉里选 Custom然后在下方 Units 里把单位切成 mm再按实际面单量出来的宽高填进去。单位这一步千万别跳过默认单位可能是 inch 或 pixel填 100 进去出来就是 100 英寸打印机直接走纸错乱。面单版式的通行做法是只保留 Detail 区域其他区域高度全部设为 0。原因在于面单上没有“页眉页脚”的概念Title、pageHeader、columnHeader、columnFooter、pageFooter、Summary 只要有高度就会把 Detail 往下推最终打印位置整体偏移。把用不到的 Band 高度设 0 而不是删掉是为了保留结构、后续改版时还能把内容放回去。2.5 需求到 Band 的映射表需求落点 Band关键设置报表名称、只出现一次Title高度按内容给第二页起自动消失页码、打印时间、打印人pageHeader 或 pageFooter用内置变量$V{PAGE_NUMBER}取页码列标题columnHeader放 Static Text 或字段标签不放数据字段明细数据行Detail高度决定每页行数本页小计pageFooter变量 resetType 设为 Page全表合计Summary变量 resetType 设为 Report按店铺/月份分组groupHeader / groupFooter右键报表名 Create Group最后一页签收栏lastPageFooter设置后替换最后一页 pageFooter快递面单只留 Detail纸张 Custom 单位 mm 其余 Band 高度归 0提示Band 高度设 0 之后在设计器里看不见但在大纲树里还在改版时从大纲选中再改回数值即可不要直接删除 Band。3. 把 JSON 接成可预览的报表字段兜底、序号变量与类型转换以 JSON 作为常用数据源时整条链路是有固定顺序的添加数据源主要供预览使用没有数据源也能生成报表、新建报表页、关联数据源、添加数据字段、添加并修改字段标签、属性设置、分组、预览、编译。顺序颠倒会带来一堆莫名其妙的空值问题比如先拖控件再关联数据源字段表达式里的路径就对不上。这一章按这条链路走一遍重点讲三个最容易卡住的地方字段漏了怎么补、序号列怎么做、交叉表要的 Integer 类型怎么改。3.1 数据源与字段的创建顺序先准备一份能代表最终结构的 JSON 样本字段名和类型都按真实接口来不要用a、b、c占位{ receiptCode: RK202401001, shipFromName: 某供应商, itemCode: IT-0001, totalQty: 120, openQty: 20, closedAt: 2024-01-18 10:22:31 }在设计器里新建数据源类型选 JSON指向这个文件或填一个可访问的地址。接着新建报表页在向导里关联刚建好的数据源向导会把 JSON 的每个键解析成一个 field。这里有个细节向导解析出来的类型是根据样本值推断的totalQty: 120会被识别成 Integer但如果样本里写成120就会被识别成 String后面做 Sum 统计会直接失效。所以样本文件里的值类型必须和真实数据一致这是最容易留下隐患的一步。关联完成后把字段拖到 Detail 区设计器会自动生成对应的 Text Field 和字段标签。标签默认是字段名需要手工改成中文列名改的时候在属性面板的 Text 属性里改不要直接改字段的 Name——Name 是和数据源绑定的键改了之后取不到值。字段标签和字段 Name 是两个概念这一点在 3.2 节还会再强调一次。3.2 漏字段兜底fields 右键新增与 Name 对齐JSON 数据源用久了总会遇到字段漏掉的情况接口加了新字段但报表模板是早前建的向导解析时没有它。这时不用重建数据源直接在左下角的报表大纲里右键 fields选择新增字段然后在属性面板里重命名 Name 和描述并选择对应的字段类型。关键约束只有一条Name 必须与数据库查询字段保持一致大小写也要一致。JSON 是大小写敏感的totalQty和totalqty在两个世界里完全不是一回事。类型同样要对日期字段选java.util.Date或java.sql.Timestamp数量字段选java.lang.Integer如果类型选错预览时表现为空白或者null而不是报错排查起来很费时间。字段补完之后记得把新字段拖进 Detail 区并改中文标签然后重新编译。设计器里的预览用的是编译后的.jasper文件只改 jrxml 不编译预览看到的还是旧结构这个失败情况我在第 5 章会单独展开。3.3 序号列用 Variable 做自增不要手写文本明细里加序号列是高频需求常见做法是利用报表大纲里的 Fx 函数区右键选 Create Variable建一个整型变量然后在属性面板里设置名称、类型、初始值和表达式。对应的 jrxml 片段是这样!-- 明细行序号按页重置每页从 1 开始 -- variable nameROW_NO classjava.lang.Integer resetTypePage initialValueExpression![CDATA[Integer.valueOf(0)]]/initialValueExpression variableExpression![CDATA[$V{ROW_NO} 1]]/variableExpression /variable逐项说明name是变量名拖到 Detail 区时用$V{ROW_NO}引用class必须是包装类型java.lang.Integer写int会编译不过resetType决定重置时机Page表示每页从 1 重新开始改成Report就是全表连续编号改成组名则是每组重新计数initialValueExpression给初值 0配合variableExpression里的加 1第一行输出就是 1variableExpression里做的是“读自己、加一、写回”这是 Jasper 变量的标准自增写法。建好之后直接把变量拖到 Detail 区作为第一列显示字号和对齐方式和其他列保持一致。这里有个常见误用拿$V{PAGE_NUMBER}当行号。它是页码变量一页里所有行拿到的值都一样做出来就是“1、1、1、1”。行号必须自己建变量页码才用内置的那个。3.4 交叉表前置把 String 改成 Integer做交叉表Crosstab之前有一个必须提前处理的类型问题。如果统计字段在 JSON 里读过来默认是 String比如ReceiptCount或者TotalQty那么在 Crosstab 向导里把 calculation 设成 Sum 之后合计结果会不对甚至整块空白。原因是 String 没有求和语义聚合函数拿不到数值。处理方式是在报表大纲里选中该字段把类型改成java.lang.Integer对应的 jrxml 是!-- 参与 Crosstab 求和的字段类型必须是数值型 -- field nametotalQty classjava.lang.Integer fieldDescription![CDATA[totalQty]]/fieldDescription /fieldfieldDescription里写的是数据源中的键路径class是目标类型。改完类型后要重新编译再进 Crosstab 向导向导才会把它识别为可聚合字段。这一步的顺序不能反先进向导再改类型向导生成的聚合表达式仍然是按 String 处理的得把 Crosstab 删掉重做。3.5 分组分页的两种做法报表明细分组有两种常见实现路径效果接近但适用场景不同。第一种在报表大纲里右键报表名选择 Create Group选中分组字段。然后在右下角属性面板里找到 band properties把 Height 设为 0并勾选 Start New Column、Start New Page 前面的复选框。Height 归零是为了让分组带本身不占版面勾选分页则是让同一组的明细归到同一页。注意 Height 要填数字 0不是删掉这个 Band删掉之后分组关系也跟着没了。第二种针对单个分组字段直接改 columnHeader把 columnHeader 的 Height 设为 0同时把 columnHeader 里原有的列名全部移到 groupHeader 中分组字段在创建分组时已经选过也可以一并放进来、必要时隐藏显示。这样做的实际效果类似“按店铺分页”——同店铺的明细归档在同一页页首直接是列名没有多余的空白带。两种做法都要配合变量重置。组统计变量的 resetType 要设成组名而不是 Page 或 Report否则每组的合计会累加上一组的数据最终数字越往后越大这种错误在预览阶段很容易被忽略因为前几页看着都对。4. PDF 里的中文字段为什么消失全局 pdfStyle 与四个参数“预览有字、导出没字”是 Jasper 报表制作里最经典的一个问题也是新手最容易怀疑自己操作有误的地方。这一章把它拆开先讲清楚为什么屏幕渲染和 PDF 渲染是两套逻辑再给出全局 style 的具体写法和四个参数的含义最后说明单个标签的局部设置与排查顺序。4.1 预览正常、导出掉字的成因设计器里的预览走的是屏幕渲染用的是操作系统已经装好的字体中文标签能正常显示是理所当然的。PDF 导出走的是另一套字体子系统它只认 PDF 规范里内置的标准字体或者被显式嵌入的字体文件。中文不在 PDF 标准字体集合里如果不指定 CJK 字体名和对应编码导出时这些字符就没有可用的字形结果就是空白或者方框。原文里描述的现象很准确不设置的话在报表预览中可以显示但上传到系统中字就没了。原因在于系统侧导出 PDF 时用的是服务端环境服务端往往没有设计器机器上那套字体回退链屏幕渲染那一层兜底彻底失效问题就暴露出来了。所以字体配置不能靠“我这台机器上看着没问题”来判断必须显式写进模板。4.2 全局 style 的 XML 写法与参数说明最省事的办法是在 jrxml 的源码界面里插入一个全局 style让所有没有单独指定字体的元素都继承它!-- 全局 PDF 字体样式放在 jasperReport 根节点下isDefault 让所有元素默认继承 -- style namepdfStyle isDefaulttrue pdfFontNameSTSong-Light pdfEncodingUniGB-UCS2-H isPdfEmbeddedtrue/四个参数逐个解释参数取值作用pdfFontNameSTSong-Light指定 PDF 导出时使用的中文字体名必须是 PDF 子系统认识的名字pdfEncodingUniGB-UCS2-H中文编码方案决定字符到字形的映射表写错会出乱码或空白isPdfEmbeddedtrue是否把字体嵌入到 PDF 文件里嵌入后换机器打开也不掉字isDefaulttrue作为默认样式所有未单独指定字体的元素自动继承isDefaulttrue是这套配置能一次性解决问题的关键。如果只对某一个 Text Field 单独设字体那么模板里几十个中文标签得挨个设一遍漏一个就是一个空白块而且后续新增字段时还得记得再设一次。设成默认样式之后全局生效新增字段自动继承维护成本降到最低。4.3 单个标签的 Advanced 面板设置与替换顺序个别标签需要不同字体时——比如标题要用黑体、正文用宋体——可以单独设置。选中标签在属性框里切到 Advanced 面板找到 PDF Font name、PDF Encoding、Embedded 三项分别填字体名、编码和是否嵌入。这和在源码里给单个元素写pdfFontName属性是等价的只是图形化的入口更直观。这里有个顺序问题值得说清楚全局 style 和单标签设置冲突时单标签的优先级更高。所以正确流程是先把全局 style 配好、导出验证一遍确认所有中文都正常再去逐个调整需要特殊字体的标签。反过来先调单个标签、最后加全局样式很容易出现某个标签被之前的局部设置覆盖、看起来“全局样式没生效”的假象。排查时按这个顺序走先看 jrxml 源码里有没有pdfStyle且isDefaulttrue再看目标标签有没有被单独设成了别的字体或空值然后核对pdfEncoding是不是UniGB-UCS2-H最后确认导出 PDF 的那台机器上字体包是否就位。四步走完还没解决基本可以判定问题不在字体而在 5.1 节讲的编译缓存上。5. 五个高频失败情况从掉字到走纸偏移的排查顺序前面几章讲的是“应该怎么配”这一章讲“配完了还是不对怎么办”。下面五条都是从实际项目里攒出来的每条按「现象 → 原因 → 解决」写顺序大致按出现频率排。遇到问题时从上往下对照多数情况能在前三步定位。5.1 上传到系统后中文字段变空白现象设计器里预览、导出 PDF 都正常模板上传到系统后页面上所有中文标签消失数字和英文还在。原因模板里只对个别标签设了字体没有全局默认样式或者加了全局样式但上传的是旧的编译产物。系统侧导出走的是服务端字体环境缺一个标签就缺一块。解决在 jrxml 源码里补上pdfStyle且isDefaulttrue保存后重新编译确认生成的.jasper时间戳是新的再重新上传。上传后先别急着走完整流程直接在系统里导一页 PDF 看中文是否完整这一步能省掉后面大量返工。5.2 预览有数据系统里查不出记录现象设计器预览时明细行满满一屏上传配置后在系统里查询结果是空表或者只有表头。原因打印配置里的条件 JSON 和 SQL 里的命名参数对不上。field、id、name、type四个是必填项field和id尽量保持一致type决定 SQL 里这个参数按什么类型传日期字段写成stringSQL 里又用日期比较条件就永远不成立。解决把打印配置里的字段名和 SQL 里的:param逐个对照重点查大小写和类型。日期、时间必须是date或datetime入库单号、供应商名才是string。改完之后重新保存配置再进查询界面验证。5.3 分组之后每页只有一行或者多出空白页现象设置了按店铺分组并勾了分页结果每组第一页只有一行数据后面跟着大片空白或者每组之间多出一整页空白。原因分组 Band 的 Height 没有归零groupHeader 自身占了一段高度把 Detail 往下推另一种情况是 columnHeader 还留着高度而列名已经移到 groupHeader等于两份列名各占一次空间。解决进属性面板把 groupHeader 的 Height 设为数字 0勾选 Start New Page如果用的是第二种做法把 columnHeader 的 Height 也设为 0确认列名只在 groupHeader 里出现一份。改完预览翻几页看每组的分页位置是否正确。5.4 Crosstab 明细显示不全或者合计是空的现象交叉表拖进 Summary 区后行列表头出来了但明细数字挤在一起看不全或者合计列一片空白。原因两个独立问题。显示不全是默认字体太大深蓝色 Static Text 和明细字段都是默认字号塞进交叉表格子里放不下合计为空是统计字段类型还是 StringSum 聚合拿不到数值。解决先回大纲把参与统计的字段类型改成java.lang.Integer重新编译后再做 Crosstab然后选中 Crosstab 中全部明细字段把字体大小调到 6 号、对齐方式设为居中。最后把 Crosstab 拉大覆盖到整个界面双击进去改深蓝色 Static Text 的上部、左部字段名改成业务上看得懂的中文。5.5 面单打印走纸偏移、内容被截断现象面单打印机打出来内容整体偏左上或者偏下右边和下边的条码被切掉一截。原因纸张格式设了 Custom 但单位没改成 mm填进去的数值被当成英寸或像素或者 Title、pageHeader、pageFooter 这些区域还留着默认高度把 Detail 整体往下推了几毫米。解决点页面最边框外侧进入 Report 属性Edit Page Format 里 Format 选 Custom、Units 选 mm宽高按实际面单量出来的毫米数填然后把除 Detail 以外的所有 Band 高度设为 0。打印机侧再把纸张规格设成和模板一致两边对齐之后偏移就消失了。6. 模板挂上菜单之前打印配置 JSON、SQL 条件与三步验证模板编译完之后还有一段路要走上传、配置打印参数、挂到菜单上。这段流程在作业报表和管理报表上略有差别前者结构固定后者要支持用户自定义查询条件差别全在条件 JSON 和 SQL 的参数绑定上。6.1 打印配置里的条件 JSON 结构作业报表的配置基本是模板化的除了name之外其他字段几乎不用动。管理报表则要按查询需求补条件。以入库商品明细为例用户要按起止日期、商品编码、供应商名查询条件 JSON 大致是这样[ { 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: code, id: code, name: 入库单号, type: string }, { field: shipFromName, id: shipFromName, name: 供应商名, type: string } ]field、id、name、type四个是必填项field和id尽量保持一致type为h1的那一条是分组标题不参与查询只负责在界面上把条件分块。这个type必须和下一步 SQL 里用到的条件字段类型严格对应日期填date、文本填string填错不会报错只会让查询条件静默失效。6.2 SQL 里 :param 的写法与 IS NULL 兜底条件定义好之后SQL 里的参数名要和field一一对应并且每一项都要加空值兜底否则用户不填某个条件时整条查询会返回零行SELECT rh.closedAt, rd.receiptCode, rh.shipFromName, 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.trailingSts 900 AND (rh.code :code OR rh.code IS NULL) AND (rd.itemCode :itemCode OR rd.itemCode IS NULL) AND (rh.shipFromName :shipFromName OR rh.shipFromName IS NULL) AND (rh.closedAt :beg OR rh.closedAt IS NULL) AND (rh.closedAt :end OR rh.closedAt IS NULL) ORDER BY rh.closedAt DESC每个条件都写成字段 :参数 OR 字段 IS NULL的形式含义是“参数没传时这一条恒真不参与过滤”。少了这个兜底只要用户漏填一个条件整个 WHERE 链就断了。6.3 上线前的三步验证模板上传完成后在开发菜单里找到报表对应的界面类型和界面名称在 View JSON 里打开加载项在打印区域添加对应作业报表文档名称对应模板名称、id 对应打印配置里的代码名点更新。这一步只适用于一般作业报表管理报表直接在报表查询里双击预览选好自定义期间和其他字段点 PDF 下载验证。我自己的验证习惯固定为三步先在设计器里导出一次 PDF确认中文不掉字再在系统里跑一次不带任何条件的查询确认基础数据能出来最后带上起止日期和编码跑一次确认参数绑定生效。从那以后我每次改完模板都强制走一遍「源码加全局 style → 重新编译 → 导出 PDF 看中文 → 系统里带条件查一次」这四步再没在交付现场翻过车。希望帮到你。本文还有配套的精品资源点击获取