iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
Software Development

Java工程師的報表入門與實作系列 第 14

JasperReports-SubReport 子報表(上)

  • 分享至 

  • xImage
  •  

SubReport元素

  • 在JasperReports中,子報表(SubReport)是相對較難的設計方法,在主報表中嵌入一個甚至多個子報表,讓複雜的資料能更靈活的展示在報表中。在我的經驗中SubReport確實是最不好懂得元素,但是學會之後就發覺沒有想像中的困難。
  • 我們可以在右上區域找到SubReport元素,SubReport意味著報表中的報表,因此一個SubReport需要對應一個報表模板。

使用子報表有以下的優點:

  1. 簡化複雜報表邏輯
    我們能將邏輯分散到不同的子報表中,每個子報表專注於不同的資料展示,可以有效降低主報表的複雜度。會使用子報表通常是讓主報表的資料作為匯總資料,而細部或不同邏輯的資料由子報表佈局。

  2. 提高維護性
    如果有需求變更時,修改單個子報表會比重新設計整個報表更容易。尤其在報表欄位非常多的狀況下,光是調整每個欄位的位置就很花時間,如果將相同邏輯的資料放入子報表,只要移動子報表就可以一次移動所有同類型的資料,提高了報表的維護性。

  3. 處理多個資料來源
    子報表也是一張報表,因此主報表和子報表可以分別使用不同的資料來源,在使用不同API的回傳資料彙整到同一張報表上的時候很好用。

接下來用一個簡單的情境來看看子報表如何使用

情境:科系課程平均成績表

這次我想查看學校各科系的學生,在各科目的平均成績,希望的格式如下:

在課程與成績平均欄位顯示各科系所修的課程與成績平均,現在DB的資料很簡單,各科系修的課都一樣,但如果修的課不同,每個科系對應的資料數量就會不同,如果是不用子報表的情況(模板如下圖),Detail中即使科系是相同的資料,也會因為與每個不同的課程資料是同一個dataSource而重複顯示


因此想做到希望的格式,有合併儲存格的效果的話,要將「科系」放在主報表,而「課程」與「成績平均」放到子報表,用不同的dataSource放入報表中。

設計含子報表的模板

  • SubReport也是一種元素,通常是顯示資料,因此常常會放在Detail Band中
  • 先將原先模板的「課程」與「成績平均」的Text Field與Field都刪除,由右上區域拖曳「SubReport」元素到Detail,並調整好大小
  • 前面提到SubReport需要對應一個報表模板,因為它也是一個報表,所以我們要新增一個空白模板。(File -> New -> JasperReport)
  • 由於SubReport是放在Detail Band中,子報表的Band必須和主報表是符合的,因此必須把子報表除了Detail以外的Band都刪除(在Band點右鍵 -> Delete),最後子報表的模板如下圖:
  • 子報表是嵌入在主報表中的,所以子報表的模板長寬必須與主報表中SubReport的長寬一致。
    我的主報表中SubReport的長寬為260px與30px

    子報表模板的長寬也設為260px與30px,並且要勾選「ignore pagination」 ,不讓子報表分頁
  • 同理,子報表中Text Field的長度必須要跟主報表的欄位寬度一致。將「課程」與「成績平均」的Text Field與Field新增在子報表的模板中時要注意調整大小與位置
    算平均時可能有小數點,所以averageScore的型別我是用BigDecimal。

設定子報表屬性

有很多種設定屬性的方式都能成功配置子報表,我介紹的方法不是唯一的方式。

  • Expression: 現在子報表與主報表是兩個還沒有關聯的模板,要如何將子報表模板指定到主報表中,可以利用「Expression」屬性。

    • 新增一個參數,用來放置子報表的「JasperReport」,也就是將jrxml模板編譯後的jasper文件,因此我命名為compiledSubReport,型別則必須是「net.sf.jasperreports.engine.JasperReport」,我的目的就是利用REPORT_PARAMETERS_MAP將編譯後的子報表文件對應到主報表
    • 我在這系列中的範例都是用程式JasperCompileManager.compileReport()來編譯,其實也可以用Jaspersoft Studio自己的編譯功能,可以直接點選模板上方的icon來編譯,編譯後會產生一個xxx.jasper的檔案,就是編譯後的jasper文件,後端程式處理報表生命週期時要使用xxx.jasper這個檔案的路徑
    • 但只要報表有修改,就要記得手動點擊icon來編譯,如果忘記手動編譯,報表仍然會是修改前的版本,所以我比較偏好使用JasperCompileManager編譯
  • Parameters Map Expression: 這是用來對應子報表自己的REPORT_PARAMETERS_MAP到主報表的,這次的範例沒有用到,要設定的話我也是用主報表的參數來對應

  • Connection Expression: 這似乎是用來讓子報表與資料庫連結的,我沒有用過,就交給其他報表大師介紹囉

  • Data Source Expression: 這是用來對應子報表的dataSource的,要讓多種不同dataSource彙總在主報表就是靠這個屬性
    考量子報表的「課程」與「成績平均」是用主報表的「科系」歸類在一起,因此我想利用departmentId讓同一類的子報表資料能順利對應到正確的科系,大致上的過程如下圖:

    • 在主報表新增「departmentId」這個Field,之後在後端以Java將主報表dataSource與子報表dataSource以此屬性歸類,這樣在處理資料時,能夠依據departmentId將dataSource正確分類
    • 後端程式會將歸類後的資料,以departmentId為key,子報表dataSource為value放入REPORT_PARAMETERS_MAP
    • 我的Data Source Expression的表達式為$P{REPORT_PARAMETERS_MAP}.get($F{departmentId}),在報表編譯時JasperReports會自動取得主報表中departmentId這個Field的數值,以map.get()取得此departmentId對應到的子報表dataSource
    • 但是REPORT_PARAMETERS_MAP這個預設的參數型別固定是Map<String, Object>,必須注意在後端操作時要將原先的型別轉為String,而我的departmentId Field的型別也必須是String,不然會拋出例外

報表模板的準備就到這告一個段落,光是Jaspersoft Studio的設定就有點複雜了,後端的邏輯就留給下一篇吧


Reference


上一篇
JasperReports-報表沒有資料時的處理
下一篇
JasperReports-SubReport 子報表(下)
系列文
Java工程師的報表入門與實作15
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言