简介:面向使用 Jasper 报表工具进行业务报表设计与打印输出的 IT 从业人员和报表初学者,这份文档围绕报表基础结构与常见制作技巧展开,帮助读者理解标题、页眉页脚、明细、汇总、分组等区域的作用,并覆盖 JSON 数据源、PDF 中文字段、手工补充字段、明细分组、序号列、自定义纸张及交叉报表等实用主题。包内共 1 个 docx 文件,约 599KB,以文字讲解和要点整理为主,内容兼顾结构说明与场景提示,便于在遇到字段缺失、中文打印异常、分组分页和面单纸张设置等问题时快速定位思路。当前已有 242 人学习,适合作为日常报表设计时的参考笔记,也可用于查漏补缺,提升报表排版与数据展示效率。文档篇幅精简、重点明确,适合放在手边随时翻阅,并沉淀为团队可复用的报表制作经验。
1. 中文标签在预览里好好的,传到系统里就没了
Jasper 报表制作这活儿,装环境那一步很少有人认真记录,等出事才发现坑全埋在配置里。我见过最常见的一幕:Jaspersoft Studio 里预览中文正常,编译、上传、挂进菜单,业务点一下 PDF 下载,打开一看中文标签全空白,只剩数字和英文活着。这不是报表画错了,是 PDF 字体和编码没配。下面按一台干净机器的顺序走:先把 Studio 6.x 和 JDK 装对,再讲 JSON 数据源、字段类型、分组、序号列、面单纸张和交叉表,最后是上传挂载与出事怎么救。适合要出 PDF 面单和管理报表的开发者,也适合接手别人模板、被一句“照着改改”丢进项目的运维。
2. 装 Jaspersoft Studio 6.x:JDK、工作空间和九个 Band 先对上
装 Studio 之前在纸上写下来:你打算用哪个 JDK、工作空间放哪、数据源是本地 JSON 文件还是接口地址。三件事没定,后面一定返工。这一章先把运行环境和界面结构固定住,后面两章的配置才有参照物。
2.1 版本与 JDK 怎么搭
Jaspersoft Studio 6.x 这条主线,社区版和企业版界面几乎一样,做报表模板用社区版就够。要留意的是 JDK:6.x 早期分发包自带 JRE,双击就能开;后来的 6.x 版本默认吃本机 JDK,机器上没装、或者装的是太新的版本,双击后闪一下就没,日志里常见Unsupported class file major version这类字样。我一般固定用 JDK 8 跑 6.x,稳,遇到需要更高版本 JDK 的 6.x 包再单独切,不在一台机器上混着来。
# 先看当前 PATH 里生效的是哪个 JDK java -version # Linux / macOS 再看 JAVA_HOME 指向,Windows 在系统属性里查 echo $JAVA_HOMEjava -version输出1.8.0_xxx说明 PATH 里是 JDK 8,可直接配 6.x 主线;输出 11、17 而手上的 Studio 是吃 JDK 8 的版本,就得回去改JAVA_HOME和 PATH 顺序,而不是反复重装 Studio。JAVA_HOME指向的是 JDK 根目录(里面有bin、lib那层),不是bin目录本身,这一条写错的人不少。装完 Studio 再改 JDK,记得重启 Studio,只重开报表窗口不会重新加载 JVM。
还有一种情况:机器上装了多个 JDK 给不同项目用。常见做法是不动全局 PATH,给 Studio 的启动参数或安装目录下的配置文件单独指定 JVM,或者在启动前临时切JAVA_HOME。混着用的后果是同一份 jrxml 在你这能跑、在测试机报错,排查起来很费时间。
2.2 安装与首次启动:工作空间别放同步盘
Windows 拿安装包一路下一步,macOS 拖 dmg,Linux 解压后跑目录里的可执行文件,三条路殊途同归。真正要小心的是首次启动弹出来的那个 Workspace 选择框。
工作空间路径要求三条:全英文、不带空格、不在任何同步盘里。OneDrive、iCloud、坚果云这类目录会实时锁文件,Studio 自动保存和编译时会撞上文件占用,表现是随机报Resource is out of sync或者保存后内容回退,很难复现,也很难查。路径里带中文或空格,某些版本的资源解析会直接失败,报的错还跟路径无关,属于玄学级排错。
# 推荐的工作空间路径长这样,纯英文、无空格、本地盘 /Users/dev/workspace-jasper D:\workspace\jasper选好路径后勾上“Use this as the default and do not ask again”,省得每次启动都弹。进主界面第一件事改编码:Window > Preferences > General > Workspace > Text file encoding选 UTF-8。这一步不做,模板里的中文标签在别的机器上打开可能是乱码,尤其是团队协作、模板要进版本库的时候。改完编码再新建报表,顺序不能反。
2.3 界面分区与 Band 结构:一张表看清谁该放什么
主界面实际就四块在用:中间是设计区(Design 页签)和源码区(Source 页签),左边是 Outline 大纲树(Fields、Variables、Parameters、Groups 都在这),右边是 Properties 属性面板,下边或右侧是 Preview 预览。Studio 基于 Eclipse,快捷键那套直接继承:Ctrl+S 保存、Ctrl+Z 撤销、Ctrl+Shift+R 找资源文件,切 Design 和 Source 两个页签是全天最高频的动作。
批量改样式我基本不在面板里点,直接切 Source 改 XML,改完切回来,比一个个字段点属性快一个数量级。但要注意 Source 里手改过的内容,面板不一定会同步刷新,改完保存再切页签一次比较稳妥。
设计区那些横条叫 Band,决定了内容出现在哪一页、哪个位置。这是整篇里最该先背下来的东西:
| Band | 出现位置 | 适合放什么 | 常见误用 |
|---|---|---|---|
| Title | 只在第一页最上方 | 报表大标题、单位名 | 把每页都要的表头放进来 |
| pageHeader | 每页上部,第一页在 Title 下方 | 页码、打印时间、打印人 | 塞业务数据字段 |
| columnHeader | 每页列标题行 | 列名,不是列数据 | 把明细字段拖进来 |
| Detail | 每页重复出现 | 明细行,如逐条收货记录 | 放合计公式 |
| columnFooter | 每页列级统计 | 本页列小计 | 放整表合计 |
| pageFooter | 每页最下端 | 页脚说明、页码 | 放跨页统计 |
| Summary | 最后一页 Detail 之后 | 整表合计、交叉表 | 只留几行空 band |
| groupHeader | 每个分组开头 | 分组说明,如店铺名、月份 | 高度没归零导致空白页 |
| groupFooter | 每个分组结尾 | 组内小计 | 与 columnFooter 统计口径混淆 |
| lastPageFooter | 最后一页最下端 | 末页专属说明 | 与 pageFooter 同时启用导致重复 |
新建报表向导默认给的是 Title、pageHeader、columnHeader、Detail、columnFooter、pageFooter、Summary 这一套,groupHeader 和 groupFooter 要建分组才出现,lastPageFooter 需要手动在大纲里加。做快递面单时,我用到的只有 Detail 一个 band,其余全部高度设 0,这个后面单独讲。
3. 接 JSON 数据源:字段、类型和中文字体一次配到位
工作室装好了,下一步是把数据接进来。用 JSON 当数据源的场景比 JDBC 多,接口返回什么直接拿来用,少一层数据库驱动。顺序错了会出现“预览没数据”“字段拖不出来”这类问题,其实都不是 bug,是步骤跳了。
3.1 从空白报表到能预览的固定顺序
我固定按这七步走,一步都不省:
- 建数据源适配器:Repository Explorer 里右键
Data Adapters > Create Data Adapter,选 JSON File 或 JSON 从远程地址,指向 JSON 文件或填接口地址。 - 新建报表:Blank A4,向导里选刚建好的适配器,让它自动读字段。
- 关联数据源:设计区点空白处,属性面板确认
Dataset引用的是这个适配器。 - 把 Fields 从 Outline 拖到 Detail,Studio 会自动生成“标签 + 文本字段”配对。
- 改标签文字,中文标签在这一步写进去。
- 分组、算合计、调列宽。
- Preview 里选数据源预览,确认有行、中文正常,再保存 jrxml。
JSON 适配器里有个XPath expression容易被忽略。根节点直接是对象或数组时填/;数据被包在某个数组里,比如{"items": [...]},就填/items。填错了预览显示“无记录”,但字段列表是空的还是齐的,能帮你快速判断问题出在哪一层。
一份最小可用的 JSON 数据长这样,字段名后面会和 field 的 Name 严格对应:
{ "receiptCode": "RK202401001", "shipFromName": "某供应商", "closedAt": "2024-01-18 10:22:00", "itemCode": "IT-0001", "totalQty": 120, "openQty": 20 }字段名receiptCode、totalQty这类大小写必须和 JSON 里一模一样。JSON 本身区分大小写,Studio 生成的 field 名也照抄,改一个字母就查不到值,报错时只提示字段找不到,不会告诉你差了个大小写。
3.2 漏字段、类型错:手工补 field 的规矩
接口字段是别人维护的,今天加一个、明天改个名,很正常。发现某个字段没在 Fields 里,不用重建报表,Outline 里右键Fields > Create Field手工补一个就行。
规矩有三条:
Name必须与 JSON 键或查询结果列名完全一致,大小写敏感,不能用中文。中文写在Description里,给人看。Class按实际类型选,String 用java.lang.String,整数用java.lang.Integer,日期用java.util.Date。JSON 里"totalQty": 120不带引号时向导一般能识别成整数,带了引号就是字符串,后面做 Sum 会翻车。- 补完字段,Detail 区里还要拖一个文本字段出来,属性面板的
Expression改成$F{你的字段名},不拖控件只加 field 是显示不出来的。
日期字段还有一层坑:JSON 里是2024-01-18 10:22:00这种字符串,Class 选了java.util.Date直接预览会抛格式异常。常见做法是先用java.lang.String接,显示时用SimpleDateFormat转,或者属性面板的Pattern里配对应格式。类型选错在明细里不太看得出来,一到分组统计和交叉表才炸,属于典型的埋伏。
3.3 PDF 中文不显示:pdfStyle 全局样式的写法
回到开头那个翻车现场。Studio 预览用的是屏幕渲染,字体随便调;导出 PDF 走的是另一套渲染链,没指定 PDF 字体和编码,中文就是空白。原文给的做法是对每个中文标签在Advanced面板里设三项,标签一多就是体力活,所以更省事的是加一段全局样式。
<!-- 放在 jasperReport 根节点下,所有 band 之前 --> <style name="pdfStyle" isDefault="true" pdfFontName="STSong-Light" pdfEncoding="UniGB-UCS2-H" isPdfEmbedded="true"/>三个属性各管一件事:pdfFontName指定 PDF 里用的中文字体,STSong-Light是 iText 内置的宋体类字体,不用额外装字体文件;pdfEncoding必须是UniGB-UCS2-H,这是中文 Unicode 编码,写错成Identity-H之类的会出现方块或者直接丢字;isPdfEmbedded为 true 表示把字体信息嵌进 PDF,换台机器打开也不变形。isDefault="true"让所有文本字段默认继承这套设置,新拖的控件不用再逐个配。
注意:如果模板是老版本传下来的,里面可能已经对单个字段设过 PDF 字体,字段级设置的优先级高于全局 style,只加全局那段是不生效的。要先把字段级的清掉,或者干脆在 Source 里全局搜
pdfFontName,统一替换。
4. 版式调优:分组、序号列、面单纸张和交叉表
数据通了、中文能出了,接下来是让报表长得像那么回事。这一章四个动作都属于“不做也能出 PDF、做了才像成品”的类型,每一个都有对应的踩坑点。
4.1 分组两种做法:band 高度归零与表头搬家
按店铺、按月份分组打印是高频需求。做法一:Outline 里右键报表名,Create Group,选中分组字段,然后在属性面板把 groupHeader 的Band Properties > Height设成 0,同时勾上Start New Column和Start New Page。这样组标题本身不占高度,但每组强制从新页开始,效果就是同店铺的明细归在同一页。
做法二适合不想新建分组的情况:直接把 columnHeader 的高度设成 0,把原来放在 columnHeader 里的列名全部拖到 groupHeader 里。列名只在每组开头打一次,明细跟在下面,视觉上同样是分组分页。
两种做法的差别在维护成本上。做法一保留了完整的 group 结构,后面要加组内小计、组内计数,直接往 groupFooter 里拖字段就行;做法二结构简单但扩展性差,groupFooter 不存在,想算组小计还得回头补分组。我一般按“以后还要不要改”来选,一次性报表用做法二,长期维护的用做法一。
高度归零这件事有个前提:band 里的控件也要一起清掉或者移走。只把高度改成 0 但控件还留在里面,Studio 不报错,导出时会出现莫名其妙的空白页或者内容被裁切,属于典型的血泪经验。
4.2 序号列用 Variable,不要手写死数字
明细行前面加序号,肉眼一看是小事,但 Detail 是重复渲染的,写死 1、2、3 等于每行都打 1。正确做法是建变量:
Outline 里右键Variables > Create Variable,属性面板里设这几项:
| 属性 | 值 | 说明 |
|---|---|---|
| Name | seqNo | 变量名,后面表达式引用 |
| Value Class | java.lang.Integer | 计数结果类型 |
| Calculation | Count | 每命中一次表达式加一 |
| Reset Type | Report | 整个报表从头计到尾,不随页重置 |
| Increment Type | None | 不按页或分组自增,跟 Calculation 配合 |
| Expression | $F{itemCode} | 用某个必然有值的字段做计数依据 |
配完把变量从 Outline 拖到 Detail 第一列,属性面板的 Expression 确认为$V{seqNo}。如果希望每页重新从 1 开始,Reset Type改成 Page;按组分页编号就改成 Group,并指定组名。这个变量跟分页是联动的,改完一定要翻到 Preview 里的第二页、第三页确认重置行为符合预期。
有一种偷懒写法是直接用内置的$V{REPORT_COUNT},它确实存在,但含义是“报表已处理的记录数”,在分组和分页场景里容易和业务序号对不上,尤其是分组后每组想重新编号的时候。自己建一个变量,控制权在自己手上。
4.3 自定义纸张与 EQUALS 条件打印
快递面单、小票这类不是 A4 的报表,先在新建报表时选 A4,然后点设计区最外侧的页面边框(不是点 band),属性面板出现 Report 级别的设置,进Edit Page Format > Format > Custom,宽高手动填。单位在下面的Units里选,面单场景一定选 mm,选成英寸或者像素,打印机出来的尺寸会差一大截,还查不出原因。
面单一般只用 Detail 区,其他 band 高度全设 0,Title、pageHeader、columnHeader、columnFooter、pageFooter、Summary 一个不留。同时把 Detail 的宽高调成面单实际尺寸,打印时位置才正。
面单上还有一类需求:某个字段等于特定值时才打印,比如陆运件和航空件用不同标记区分。属性面板里找到该控件的Print When Expression,写:
// 只有运输方式为 E 时才打印该控件 new Boolean($F{shipType} != null && $F{shipType}.equals("E"))两个细节不能少:返回值要包成Boolean对象,老版本 JasperReports 表达式里不做自动装箱;shipType为 null 时直接调.equals()会空指针,所以判空要放在前面。等值判断还有EQUALS()这类函数写法,效果一样,但同样要处理 null,别只测有值的数据,空值那行才是最容易翻车的地方。
4.4 Crosstab 交叉表的三条硬性条件
交叉表用来做行列汇总,比如按仓库和商品类别统计收发数量。它有三条硬性条件,任何一条不满足都是白忙:
第一,参与统计的字段必须是数值类型。JSON 读进来的数量字段默认是 String,先在 Outline 里把对应 field 的 Class 改成java.lang.Integer或java.math.BigDecimal,再进向导,否则求和结果全是 0 或者直接报类型转换错。
第二,Crosstab 只能放在 Summary 区。拖到别的 band,Studio 会拒绝或者在编译时报错。所以做交叉表前先确认 Summary 还在,如果之前为了面单把这个 band 删了,得先加回来。
第三,向导里加明细字段时,Calculation要手动改成Sum。默认是 Nothing,出来的就是原值罗列,不是汇总。加完向导点完成,把 Crosstab 拖大覆盖到整个 Summary 区,双击进去改深蓝色 Static Text 的行列标题。默认字号偏大,明细通常显示不全,选中所有明细字段统一改成 6 号、居中,这一条几乎是每次都要做的收尾动作。
5. 上传挂载与常见问题排查:条件 JSON、SQL 占位符、View JSON 三处要对齐
模板在 Studio 里只是半成品。编译通过的 jrxml 上传到系统之后,还要在打印配置里登记、写查询条件、配 SQL、挂到菜单,这四步里任何一处名字对不上,前端就是点了没反应或者一片空白。这一章按配置顺序写,最后把反复出现的五类问题按“现象、原因、解决”列出来。
5.1 打印配置:代码标识与条件数组
后台路径一般是“配置 > 打印配置 > 报表 > 新增报表”,要填代码标识、描述、报表类型,再贴一段条件 JSON 和一段 SQL 模板。代码标识是这套配置的唯一名字,模板名称尽量和它保持一致,后面菜单挂载要引用它。
条件 JSON 决定了前端弹出什么查询框:
[ {"field": "", "id": "h1_basic", "name": "基本信息", "type": "h1"}, {"field": "beg", "id": "beg", "name": "起始日期", "required": true, "type": "date"}, {"field": "end", "id": "end", "name": "结束日期", "required": true, "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尽量写成一样,后面 SQL 里的占位符用的是这个名字。type决定控件形态:date弹日期选择器,string是输入框,integer只收数字;required为 true 时前端会拦空值。type为h1的那条是分组标题,field留空,用来把条件分块显示,纯作业报表其实只留标题加一个装载号就够,管理报表才需要把日期、单号、供应商这些都加上。
5.2 SQL 模板里的占位符和 null 判断
SQL 模板的占位符必须与条件 JSON 的id一一对应,大小写一致:
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 (:code is null or rh.code = :code) and (:itemCode is null or rd.itemCode = :itemCode) and (:shipFromName is null or rh.shipFromName = :shipFromName) and (:beg is null or rh.closedAt >= :beg) and (:end is null or rh.closedAt <= :end) order by rh.closedAt desc每一行过滤都写成“参数为空就不过滤”的形态,用户不填单号就查全部,填了就精确匹配。这种写法比把条件拼成字符串安全,也不用手动判断要不要加and。
注意:部分数据库驱动对
:beg is null这种判断推断不出参数类型,会报参数类型无法确定。遇到这种情况,常见做法是把日期参数在执行前补一个默认值(比如很早的日期),或者改成字符串比较,先让语句能编译通过,再回头调参数类型。
(rd.totalQty - rd.openQty) as redQty这种计算列在 SQL 里算完,报表里直接当普通字段用,比在报表表达式里再写一遍公式好维护,两边口径也不会打架。
5.3 菜单配置 View JSON 与管理报表查询
作业报表要在前端菜单上点得出来,得去“开发 > 菜单配置”找到对应的界面类型和界面名称,在 View JSON 里找到打印区域,把报表挂上去:
{ "print": [ {"name": "入库商品明细表", "id": "receiptDetailReport"} ] }name对应上传的模板名称,id对应打印配置里的代码标识,两个都填错或者只错一个,前端按钮就在但点了没反应。改完点更新,重新打开页面才生效,别在原地刷新等。
管理报表不走这条路,它在“管理 > 报表查询”里直接列出来,双击就是预览界面,选好自定义期间、填完其他条件,点 PDF 下载即可。作业报表和管理报表的差别就在这:前者挂在具体业务单据的详情页上,带单据主键;后者是独立查询入口,条件是用户自己选的。
5.4 五条踩坑记录:从现象倒推原因
现象一:Studio 里预览中文正常,系统里下载的 PDF 中文全空白。原因:模板没设 PDF 字体和编码,屏幕渲染能显示,导出就丢。 解决:加全局pdfStyle那段,或者逐字段在 Advanced 面板设pdfFontName、pdfEncoding、isPdfEmbedded三项;老模板记得先清掉字段级覆盖。
现象二:编译通过,预览或运行时报字段找不到、空指针。原因:JSON 里漏了字段,或者手补的 field 名字大小写、拼写与数据源不一致。 解决:Outline 里Create Field补上,Name 严格照抄数据源,Class 选对;补完确认 Detail 里有控件引用$F{...}。
现象三:分组后每页多一张空白页,或者组标题不换页。原因:groupHeader 的 Height 改了 0,但里面的控件没清;或者没勾Start New Page。 解决:band 高度归零前先把内容移到 groupFooter 或 Detail,勾选Start New Column、Start New Page,预览翻三页确认。
现象四:交叉表空白、合计全是 0。原因:统计字段还是 String 类型,或者向导里 Calculation 没改成 Sum,或者 Crosstab 放错了 band。 解决:field 的 Class 改成 Integer,向导中明细字段的 Calculation 统一 Sum,确认 Crosstab 在 Summary 区,最后把明细字号调到 6 号居中。
现象五:面单打印位置偏、内容被裁。原因:页面格式没设 Custom,单位不是 mm,或者非 Detail 的 band 高度没清零。 解决:Report 属性进Edit Page Format > Custom,单位选 mm 填实际尺寸,其余 band 高度全部设 0,打印机纸张同步设成一样大小。
6. 装完怎么验证跑通:一份最小 JSON 报表的四步验收
装完不要直接上手改正式模板,先做一份最小报表把链路走通。我一般用一份三行数据的 JSON、只保留 Title、columnHeader、Detail、Summary 四个 band,字段就三个:单号、供应商、数量。小到极致,出问题一眼能定位。
验收分四步,每步都拿结果说话:
| 步骤 | 操作 | 通过标准 | 不通过先查什么 |
|---|---|---|---|
| 1. 数据接入 | Preview 选 JSON 适配器 | 出现三行记录,无空字段 | XPath 表达式、字段名大小写 |
| 2. 中文 PDF | Preview 里用 PDF 预览模式 | 中文标签完整,无方块无空白 | pdfStyle 全局样式是否生效 |
| 3. 分组换页 | 按供应商建组,band 高度归零 | 每组从新页开始,无空白页 | Height 是否为 0、是否勾 Start New Page |
| 4. 系统下载 | 上传模板、配条件、挂菜单、PDF 下载 | 条件生效,中文正常,合计对得上 | 代码标识与 View JSON 的 id 是否一致 |
第 2 步有个技巧:别用屏幕预览判断中文是否正常。Studio 的屏幕预览用的是本机字体,即使 PDF 配置全错也能显示得好好的。切到 PDF 预览模式再看,走的是和系统同一套渲染,这一步能提前拦下八成的中文字体问题。
第 4 步之前,我还会先在“报表查询”里用管理报表入口试跑一次,因为那里能直接选参数、直接 PDF 下载,不依赖菜单配置,能把“SQL 和条件 JSON 对不上”和“菜单挂载写错”这两类问题分开看。全都通了再去挂菜单,顺序反了就会在两个错误之间来回猜。
从那以后我每次新装一套环境,都强制走一遍这四步,哪怕只是换台机器重装 Studio,也要拿这份三行数据的小报表验一遍手。省下的不是这十分钟,而是后面在业务面前打不开 PDF 的那半小时。希望帮到你。
本文还有配套的精品资源,点击获取