Table of contents
1 JasperReports 簡介
JasperReports 係一個 reporting 既工具,以純文字 template 既形式保存 report 既設計。佢亦提供相應既 Java library 畀程式去生成 report 檔案。
JasperReports 支援多種 report 檔案格式,例如 PDF、Microsoft Word、HTML,亦支援 PDF 內 embed fonts、PDF 既原生檔案加密功能。
2 開發流程
- 用一個商業 email 註冊一個 Jaspersoft community 帳號。
- 下載 Jasper Studio community 版。
- 打開 Jasper Studio,用 Jaspersoft community 帳號登入。
- 用 Jasper Studio 製作 report template。
- 將檔案保存為 JRXML 格式(亦可以用 Jasper Studio build 成
.jasper
檔案)。
- 寫一個 Java 程式去生成 report 檔案。
- 根據需要而執行呢個 Java 程式。
3 .jrxml
vs .jasper
檔案格式
檔案格式 | 描述 |
---|
.jrxml | 係裝住 template layout 設計既 XML 格式。 |
.jasper | 係 .jrxml 經過 compilation 而得出既 Java bytecode 格式。 |
4 JasperReports 6 vs 7
JasperReports 7 比起 JasperReports 6 既最大分別:
- JasperReports 7 將 JasperReports 6 既部分 API classes 分拆左出黎做新既 Maven modules。
- JasperReports 6、7 既 JRXML 檔案、Java libraries 唔相容。
- 我地可以用 Jasper Studio 7 將 JasperReports 6 既 JRXML 轉換成 JasperReports 7 既格式。
5 Report template 例子(JRXML)
JRXML:
- 係 XML 格式。
- 裝住 template layout 設計。
- 有 sub-report 既概念,可以 reference 另一個 template 既
.jasper
檔案。
- 可以用 Jasper Studio 或者 JasperReports Java library build 成
.jasper
檔案。
- 應該被視為 source code,並且保存喺 version control 工具。
5.1 JasperReports 6 既 JRXML
1<?xml version="1.0" encoding="UTF-8"?>
2<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports
5 http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
6 name="SampleReport"
7 pageWidth="595"
8 pageHeight="842"
9 columnWidth="555"
10 leftMargin="20"
11 rightMargin="20"
12 topMargin="20"
13 bottomMargin="20">
14
15 <scriptlet name="myScriptlet" class="code.MySampleScriptlet" />
16
17 <parameter name="recordCount" class="java.lang.Integer">
18 <defaultValueExpression><![CDATA[200]]></defaultValueExpression>
19 </parameter>
20
21 <queryString>
22 <![CDATA[SELECT TOP ($P{recordCount}) id, full_name, description FROM tbl_user]]>
23 </queryString>
24
25 <field name="id" class="java.lang.Integer" />
26 <field name="full_name" class="java.lang.String" />
27 <field name="description" class="java.lang.String" />
28
29 <title>
30 <band height="50">
31 <textField>
32 <reportElement x="0" y="0" width="555" height="30" />
33 <textElement>
34 <font fontName="SmoochSans" size="8" isBold="true" isItalic="false" />
35 </textElement>
36 <textFieldExpression><![CDATA[$P{myScriptlet_SCRIPTLET}.mySampleMethod()]]></textFieldExpression>
37 </textField>
38 </band>
39 </title>
40
41 <columnHeader>
42 <band height="30">
43 <staticText>
44 <reportElement x="0" y="0" width="50" height="30" />
45 <textElement>
46 <font fontName="SmoochSans" size="8" isBold="true" isItalic="false" />
47 </textElement>
48 <text><![CDATA[ID]]></text>
49 </staticText>
50 <staticText>
51 <reportElement x="50" y="0" width="100" height="30"/>
52 <textElement>
53 <font fontName="SmoochSans" size="8" isBold="true" isItalic="false" />
54 </textElement>
55 <text><![CDATA[Name]]></text>
56 </staticText>
57 <staticText>
58 <reportElement x="150" y="0" width="400" height="30" />
59 <textElement>
60 <font fontName="SmoochSans" size="8" isBold="true" isItalic="false" />
61 </textElement>
62 <text><![CDATA[Description]]></text>
63 </staticText>
64 </band>
65 </columnHeader>
66
67 <detail>
68 <band height="15">
69 <textField>
70 <reportElement x="0" y="0" width="50" height="15" />
71 <textElement>
72 <font fontName="SmoochSans" size="8" isBold="false" isItalic="false" />
73 </textElement>
74 <textFieldExpression><![CDATA[$F{id}]]></textFieldExpression>
75 </textField>
76 <textField>
77 <reportElement x="50" y="0" width="100" height="15" />
78 <textElement>
79 <font fontName="SmoochSans" size="8" isBold="false" isItalic="false" />
80 </textElement>
81 <textFieldExpression><![CDATA[$F{full_name}]]></textFieldExpression>
82 </textField>
83 <textField>
84 <reportElement x="150" y="0" width="400" height="15" />
85 <textElement>
86 <font fontName="SmoochSans" size="8" isBold="false" isItalic="false" />
87 </textElement>
88 <textFieldExpression><![CDATA[$F{description}]]></textFieldExpression>
89 </textField>
90 </band>
91 </detail>
92</jasperReport>
5.2 JasperReports 7 既 JRXML
我地可以用 Jasper Studio 7 將 JasperReports 6 既 JRXML 轉換成 JasperReports 7 既格式。
步驟:用 Jasper Studio 打開 JasperReports 6 既 JRXML 檔案,再保存,佢會自動保存成 JasperReports 7 既格式。
可以睇到 JasperReports 7 既 JRXML 檔案內容同 JasperReports 6 既 JRXML 檔案內容都有明顯既分別。
1<?xml version="1.0" encoding="UTF-8"?>
2<jasperReport
3 name="SampleReport"
4 language="java"
5 pageWidth="595"
6 pageHeight="842"
7 columnWidth="555"
8 leftMargin="20"
9 rightMargin="20"
10 topMargin="20"
11 bottomMargin="20"
12 scriptletClass="code.MySampleScriptlet">
13
14 <parameter name="recordCount" class="java.lang.Integer">
15 <defaultValueExpression><![CDATA[200]]></defaultValueExpression>
16 </parameter>
17
18 <query language="sql">
19 <![CDATA[SELECT TOP ($P{recordCount}) id, full_name, description FROM tbl_user]]>
20 </query>
21
22 <field name="id" class="java.lang.Integer" />
23 <field name="full_name" class="java.lang.String" />
24 <field name="description" class="java.lang.String" />
25
26 <title height="50">
27 <element kind="textField" x="0" y="0" width="555" height="30" fontName="SmoochSans" fontSize="8.0" italic="false" bold="true">
28 <expression><![CDATA[$P{REPORT_SCRIPTLET}.mySampleMethod()]]></expression>
29 </element>
30 </title>
31
32 <columnHeader height="30">
33 <element kind="staticText" x="0" y="0" width="50" height="30" fontName="SmoochSans" fontSize="8.0" italic="false" bold="true">
34 <text><![CDATA[ID]]></text>
35 </element>
36 <element kind="staticText" x="50" y="0" width="100" height="30" fontName="SmoochSans" fontSize="8.0" italic="false" bold="true">
37 <text><![CDATA[Name]]></text>
38 </element>
39 <element kind="staticText" x="150" y="0" width="400" height="30" fontName="SmoochSans" fontSize="8.0" italic="false" bold="true">
40 <text><![CDATA[Description]]></text>
41 </element>
42 </columnHeader>
43
44 <detail>
45 <band height="15">
46 <element kind="textField" x="0" y="0" width="50" height="15" fontName="SmoochSans" fontSize="8.0" italic="false" bold="false">
47 <expression><![CDATA[$F{id}]]></expression>
48 </element>
49 <element kind="textField" x="50" y="0" width="100" height="15" fontName="SmoochSans" fontSize="8.0" italic="false" bold="false">
50 <expression><![CDATA[$F{full_name}]]></expression>
51 </element>
52 <element kind="textField" x="150" y="0" width="400" height="15" fontName="SmoochSans" fontSize="8.0" italic="false" bold="false">
53 <expression><![CDATA[$F{description}]]></expression>
54 </element>
55 </band>
56 </detail>
57</jasperReport>
6 動手寫 report 生成程式
6.1 程式流程
- Compile JRXML 做
.jasper
檔案,或者讀取 pre-compiled 既 .jasper
檔案,會得到一個 JasperReport
object。
- 創建一個 JDBC database connection 或者 data source。
- 用 data 去 fill 個
JasperReport
object,我地可以提供 JDBC database connection 或者 data source,會得到一個 JasperPrint
object。
- Export 個
JasperPrint
object 做不同格式既 report 檔案,例如 PDF。
6.2 Project structure
src/main
/java
/code
Main.java
MySampleScriptlet.java
/resources
/fonts
/SmoochSans
SmoochSans-Bold.ttf
SmoochSans-Regular.ttf
font-config.xml
/templates
sample.jasper
sample.jrxml
jasperreports_extension.properties
pom.xml
註:
- Report templates 唔一定要放喺
src/main/resources
裡面,只要 Java code 用岩 file path 就會讀到。
- 我地會用一隻叫 Smooch Sans 既 font 黎做 custom font 既例子。
6.3 寫 JasperReports 6 既程式
6.3.1 Maven dependencies
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.21.4</version>
</dependency>
6.3.2 寫 Java code
1import net.sf.jasperreports.engine.export.JRPdfExporter;
2
3public void testGenerateReport() throws Exception {
4
5 final Collection<Map<String, ?>> data = new JsonMapper().readValue("""
6 [
7 { "id": 1001, "full_name": "Michael", "description": "This is Michael" },
8 { "id": 1002, "full_name": "Jack", "description": "This is not Jack" }
9 ]
10 """, Collection.class);
11 final JRDataSource dataSource = new JRMapCollectionDataSource(data);
12
13 final OutputStream os = new FileOutputStream("reports/sample.pdf");
14 final JasperReport reportTemplate = getTemplate();
15 final JasperPrint filledReport = fillTemplate(reportTemplate, dataSource);
16
17 try (os) {
18 exportReportAsPdf(filledReport, os);
19 }
20}
21
22private JasperReport getTemplate() throws Exception {
23
24 // XXX 揀其中一個方法
25 // 1. Runtime compilation 第一次會好慢
26 // 2. Load pre-compiled 既 .jasper 會快啲
27
28 return compileTemplate();
29 return loadTemplate();
30}
31
32private JasperReport compileTemplate() throws Exception {
33
34 final InputStream is = new FileInputStream("path/to/templates/sample.jrxml");
35
36 try (is) {
37 return JasperCompileManager.compileReport(is);
38 }
39}
40
41private JasperReport loadTemplate() throws Exception {
42 return (JasperReport) JRLoader.loadObjectFromFile("path/to/templates/sample.jasper");
43}
44
45private JasperPrint fillTemplate(JasperReport reportTemplate, JRDataSource dataSource) throws Exception {
46 return JasperFillManager.fillReport(reportTemplate, null, dataSource);
47}
48
49private void exportReportAsPdf(JasperPrint filledReport, OutputStream os) throws Exception {
50
51 final JRPdfExporter exporter = new JRPdfExporter(DefaultJasperReportsContext.getInstance());
52 exporter.setExporterInput(new SimpleExporterInput(filledReport));
53 exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(os));
54
55 exporter.exportReport();
56}
6.4 寫 JasperReports 7 既程式
6.4.1 Maven dependencies
1<dependency>
2 <groupId>net.sf.jasperreports</groupId>
3 <artifactId>jasperreports</artifactId>
4 <version>7.0.1</version>
5</dependency>
6
7<dependency>
8 <groupId>net.sf.jasperreports</groupId>
9 <artifactId>jasperreports-jdt</artifactId>
10 <version>7.0.1</version>
11</dependency>
12
13<dependency>
14 <groupId>net.sf.jasperreports</groupId>
15 <artifactId>jasperreports-pdf</artifactId>
16 <version>7.0.1</version>
17</dependency>
6.4.2 寫 Java code
JasperReports 7 既 Java code 同 JasperReports 6 冇乜分別,只係因為 JasperReports 7 將 JasperReports 6 既部分 API classes 分拆左出黎做新既 Maven modules,所以我地需要改部分 import statements 既 package references。
1import net.sf.jasperreports.pdf.JRPdfExporter; // 黎自 Jasper 7 既 jasperreports-pdf Maven module
2
3public void testGenerateReport() throws Exception {
4
5 final Collection<Map<String, ?>> data = new JsonMapper().readValue("""
6 [
7 { "id": 1001, "full_name": "Michael", "description": "This is Michael" },
8 { "id": 1002, "full_name": "Jack", "description": "This is not Jack" }
9 ]
10 """, Collection.class);
11 final JRDataSource dataSource = new JRMapCollectionDataSource(data);
12
13 final OutputStream os = new FileOutputStream("reports/sample.pdf");
14 final JasperReport reportTemplate = getTemplate();
15 final JasperPrint filledReport = fillTemplate(reportTemplate, dataSource);
16
17 try (os) {
18 exportReportAsPdf(filledReport, os);
19 }
20}
21
22private JasperReport getTemplate() throws Exception {
23
24 // XXX 揀其中一個方法
25 // 1. Runtime compilation 第一次會好慢
26 // 2. Load pre-compiled 既 .jasper 會快啲
27
28 return compileTemplate();
29 return loadTemplate();
30}
31
32private JasperReport compileTemplate() throws Exception {
33
34 final InputStream is = new FileInputStream("path/to/templates/sample.jrxml");
35
36 try (is) {
37 return JasperCompileManager.compileReport(is);
38 }
39}
40
41private JasperReport loadTemplate() throws Exception {
42 return (JasperReport) JRLoader.loadObjectFromFile("path/to/templates/sample.jasper");
43}
44
45private JasperPrint fillTemplate(JasperReport reportTemplate, JRDataSource dataSource) throws Exception {
46 return JasperFillManager.fillReport(reportTemplate, null, dataSource);
47}
48
49private void exportReportAsPdf(JasperPrint filledReport, OutputStream os) throws Exception {
50
51 final JRPdfExporter exporter = new JRPdfExporter(DefaultJasperReportsContext.getInstance());
52 exporter.setExporterInput(new SimpleExporterInput(filledReport));
53 exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(os));
54
55 exporter.exportReport();
56}
6.5 Report data 獲取方式
6.5.1 JDBC database connection
JasperReports 既 report template(JRXML)可以自帶 SQL queries。
我地只需要提供一個 JDBC database connection 黎 fill 個 JasperReport
object 就得,JasperReports 既 Java API 背後會自己執行 SQL queries。
6.5.2 Data source
另一個方式係我地先自行拎左 data,再用對應既 JRDataSource
implementation class wrap 住佢,再用佢黎 fill 個 JasperReport
object。
Data structure 一般都會係 N 個 rows,每個 row 有一樣既 fields,我地可以用:
Java data structure | JasperReports wrapper class | 描述 |
---|
List<Map<String, ?>> | JRMapCollectionDataSource | 一個 generic key-value pair 既 data structure。 |
List<XxxDto> | JRBeanCollectionDataSource | 一個特定 Java type(VO/DTO/entity class)既 data structure。 |
6.6 Custom font
jasperreports_extension.properties
:
net.sf.jasperreports.extension.registry.factory.fonts=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
net.sf.jasperreports.extension.simple.font.families.mySampleFontFamily=fonts/font-config.xml
註:mySampleFontFamily
可以係任何 property key 名。
fonts/font-config.xml
:
1<?xml version="1.0" encoding="UTF-8"?>
2<fontFamilies>
3
4 <fontFamily name="SmoochSans">
5 <normal>
6 <ttf><![CDATA[fonts/SmoochSans/SmoochSans-Regular.ttf]]></ttf>
7 <pdf><![CDATA[fonts/SmoochSans/SmoochSans-Regular.ttf]]></pdf>
8 </normal>
9 <bold><![CDATA[fonts/SmoochSans/SmoochSans-Bold.ttf]]></bold>
10 <pdfEncoding><![CDATA[Cp1252]]></pdfEncoding>
11 <pdfEmbedded><![CDATA[true]]></pdfEmbedded>
12 <exportFonts/>
13 </fontFamily>
14
15</fontFamilies>
註:
- 喺呢個例子裡面我地用左 Smooch Sans 既 custom font,而 JRXML 檔案裡面都有 reference 到
fontFamily
既 name
,即係 SmoochSans
。
- 將
pdfEmbedded
設定做 true
就可以將 custom font embed 入個 report PDF 檔案裡面。
6.7 JasperReports scriptlet
JasperReports 仲有一個功能叫 scriptlet,可以畀我地寫任意既 Java code 黎 fill data 落個 report template 度。
- Scriptlets 既 Java classes 需要 extend
JRAbstractScriptlet
或者 JRDefaultScriptlet
。
- 我地可以寫一啲 user-defined methods,然後用
$P{REPORT_SCRIPTLET}.mySampleMethod()
去 invoke 個 method。
- 喺 JasperReports 6、7,我地可以用
REPORT_SCRIPTLET
既 parameter 黎 reference <jasperReport>
既 scriptletClass
。
- 喺 JasperReports 6(7 試過唔 work),我地仲可以用
<scriptlet name>_SCRIPTLET
既 parameter 黎 reference <scriptlet>
既 class
。
MySampleScriptlet.java
:
1public class MySampleScriptlet extends JRDefaultScriptlet {
2
3 public String mySampleMethod() {
4 return "Sample Report";
5 }
6}
6.8 PDF 加密
加入呢個 method:
1private PdfExporterConfiguration getPdfConfig(String ownerPassword, String userPassword) {
2
3 final SimplePdfExporterConfiguration config = new SimplePdfExporterConfiguration();
4 config.setEncrypted(true);
5 config.set128BitKey(true);
6 config.setOwnerPassword(ownerPassword);
7 config.setUserPassword(userPassword);
8 config.setPermissions( // XXX 可以自己 fine-tune permissions
9 PdfWriter.ALLOW_ASSEMBLY
10 | PdfWriter.ALLOW_COPY
11 | PdfWriter.ALLOW_DEGRADED_PRINTING
12 | PdfWriter.ALLOW_FILL_IN
13 | PdfWriter.ALLOW_MODIFY_ANNOTATIONS
14 | PdfWriter.ALLOW_MODIFY_CONTENTS
15 | PdfWriter.ALLOW_PRINTING
16 | PdfWriter.ALLOW_SCREENREADERS);
17
18 return config;
19}
修改 method exportReportAsPdf
:
1private void exportReportAsPdf(JasperPrint filledReport, OutputStream os) throws Exception {
2
3 final JRPdfExporter exporter = new JRPdfExporter(DefaultJasperReportsContext.getInstance());
4 exporter.setExporterInput(new SimpleExporterInput(filledReport));
5 exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(os));
6 exporter.setConfiguration(getPdfConfig("<owner password>", "<user password>")); // 加呢句
7
8 exporter.exportReport();
9}
註:呢啲 code 係 JasperReports 6、7 都通用,只係 import statements 既 Java packages 會有分別。
7 Report 生成結果
執行程式之後,就會生成到一個咁既 PDF 檔案:
8 參考資料