2012-11-05

在 Spring MVC 3.1.2.RELEASE 產出 JasperReports 4.7.1 子報表(Subreport)

官方文件 17.7.4 Working with Sub-Reports 寫得不可思議的抽象,但運氣很好的,很快地在 Add example on how to configure JasperReports Sub-Reports with properties 找到解答。

Spring MVC 處理子報表並不像我之前的筆記 使用 Java 輸出資料到 JasperReports 子報表表身(Subreport Detail)那樣,由 Java 組出子報表再丟給主報表,而是在 Java 將子報表(Jasper)與子報表要用的 Data 一起丟給 Spring MVC 就可以了。

講得更精確,Data 是由 Java 丟的,而子報表 Jasper 則是透過設定的。

這邊一次就玩大一點,使用兩個子表,這樣可以引出一個有趣的問題。

Java 負責所有的 DataSource,包括主表和「所有」子表的。

@RequestMapping("/helloReport")
public String helloReport(Model model) {
  model.addAttribute("title", "這是主報表");

  // 主表
  Collection<Map<String, ?>> list = new ArrayList<Map<String, ?>>();
  for (int i = 0; i < 5; i++) {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("id", new BigDecimal(i + 1));
    data.put("title", "中文 字串 String " + i);
    data.put("date", new Date(112, 9, i + 1));
    list.add(data);
  }
  model.addAttribute("dataSource", list);

  // 子表1
  list = new ArrayList<Map<String, ?>>();
  for (int i = 10; i < 15; i++) {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("id", new BigDecimal(i + 1));
    data.put("title", "中文 字串 String " + i);
    data.put("date", new Date(112, 9, i + 1));
    list.add(data);
  }
  // 這裡一定要用 JRDataSource 包起來才行,不能用 Collection
  model.addAttribute("dataSource2", new JRMapCollectionDataSource(list));

  // 子表2
  list = new ArrayList<Map<String, ?>>();
  for (int i = 20; i < 25; i++) {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("id", new BigDecimal(i + 1));
    data.put("title", "中文 字串 String " + i);
    data.put("date", new Date(112, 9, i + 1));
    list.add(data);
  }
  // 這裡一定要用 JRDataSource 包起來才行,不能用 Collection
  model.addAttribute("dataSource2", new JRMapCollectionDataSource(list));

  return "helloReport";
}
除了子表只接受 JRDataSource 介面的物件,不能用 Collection,也不是說不能用,如果要用,那在 iReport 裡就得從 net.sf.jasperreports.engine.data.JRMapCollectionDataSource 改用 net.sf.jasperreports.engine.data.JRBeanCollectionDataSource,其他就是一般的作法,沒什麼特別。

然後透過 ResourceBundleMessageSource 的 views.properties 設定主表與子表的 Japser 位置,以及主表與子表要使用的 DataSource。
helloReport.(class)=org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView
helloReport.url=/WEB-INF/reports/helloReport.jasper
helloReport.reportDataKey=dataSource
helloReport.subReportUrls=helloReport1=/WEB-INF/reports/helloReport1.jasper\r\nhelloReport2=/WEB-INF/reports/helloReport2.jasper
helloReport.subReportDataKeys=dataSource1,dataSource2
最有趣的點來了!

分別是 helloReport.subReportUrls 與 helloReport.subReportDataKeys 的設值方式。

先看看原始檔,從 JasperReportsPdfView 一路往上追到 AbstractJasperReportsView,可以看到兩個參數,subReportUrls 與 subReportDataKeys。
/**
 * A String key used to lookup the <code>JRDataSource</code> in the model.
 */
private String reportDataKey;

/**
 * Stores the paths to any sub-report files used by this top-level report,
 * along with the keys they are mapped to in the top-level report file.
 */
private Properties subReportUrls;

/**
 * Stores the names of any data source objects that need to be converted to
 * <code>JRDataSource</code> instances and included in the report parameters
 * to be passed on to a sub-report.
 */
private String[] subReportDataKeys;
特別的地方在於它們的型別,一個是 Properties,另一個是 String[],嘿嘿,這種型別在怎麼在 views.properties 裡設定?

字串陣列還好,猜一下就知道,用逗號區隔就沒問題了;順序也不重要,重要的是名字,用在 iReport 裡參考使用。

但是 Properties 呢?怎麼在一個 Properties 裡傳入另一個 Properties?抓破頭無法理解!

神奇的作法,用一行文字代表一個 Properties 檔,也就是將一個 Properties 檔裡的每一行用 \r\n 串成一行
A=123
B=456
變成
A=123\r\nB=456
最後再當成值,只派給另一個 Property 的 key。
aKey=A=123\r\nB=456
這樣就可以同時指定子報表 Japser 檔的位置和 Data 的名稱,至於報表麼拉,請參考 使用 Java 輸出資料到 JasperReports 子報表表身(Subreport Detail)

結果如下。


---
---
---

沒有留言:

張貼留言