JasperReport是一個強大、靈活的報表生成工具,能夠展示豐富的頁面內容,并將之轉換成PDF,HTML,或者XML格式。該庫完全由Java寫成,可以用于在各種Java應用程序,包括JBoss,Web應用程序中生成動態內容。
基本介紹
JasperReport和IReport
Jasperreport是開源的,這給我們帶來很大方便,但文檔收費,可以理解。
它還有一個相關的開源工程ireport。
IReport是一個圖形化的輔助工具,能彌補JasperReport的缺陷:JasperReport僅提供了可使用的類庫,而未提供更好的開發工具。它們配合使用將會更大程度的提高效率。
應用場景
該庫完全用Java編寫,可用于各種Java應用程序,包括JBoss、Web應用程序,以生成動態內容。
它的主要目的是輔助生成面向頁面的(page oriented),準備付諸打印的文檔。
基本架構
JasperReport借由定義于XML文檔中的report 設計進行數據組織。
這些數據可能來自不同的數據源,包括關系型數據庫,collections,java對象數組。
通過實現簡單接口,用戶可以將report library插入到訂制好的數據源中,在以后我們將提到有關內容。
本文簡介
其實這是一份JasperReport Ultimate Guide的簡單翻譯以及我的理解和例子。在最后,我將描述一個我正在做的工程,將其中用到的相關信息貢獻出來。我這么做是因為當我在學這個類庫的時候苦于很少有相關的中文文檔,又或語言不詳,希望其他人不再受苦。這個文檔將分幾次貼出來,與原文檔的章節相對應。這份文檔的Word形式將在全部完成之后放在我的公開郵箱中與各位共享。
概覽
把涉及到的重要的類進行一一說明,可以參照概述目錄中的圖片。
Jasper設計
Class net.sf.jasper.engine.設計JasperDesign
這是一個未經加工的報表實例,供JasperReport Library使用。
JasperReport類庫中內置XML解析器對XML report 設計進行解析處理后,會生成該類對象。
如果你的程序不想對直接XML文件進行操作,在下面的例子noxmldesign中,有不使用XML設計文件而動態生成這個類的方法。
看看這個例子:
從getJasperDesign()方法我們可以看出,這個應用程序并沒有從XML文件里面將report 設計提取出來再生成JasperDesign類,而是直接利用JasperDesign提供的函數生成了一個報表設計。
這樣做的原因是基于靈活性的考慮。因此,你可以在程序中隨時動態生成報表,而不需要去從硬盤或網絡中讀取XML設計文件。
但通常我不這么做,因為比較麻煩,而且要對JasperReport的每個元素都非常熟悉才行。
JasperReport
Class net.SFjasper.engine.JasperReport
這個類的實例包含了一個經過編譯的report 設計對象。
生成它的時機是對報表編譯之后,但尚未對其填入數據的時候。
編譯過程中,JasperReport需要生成一個臨時的類文件,用以保存report expression,如變量表達式,文本,圖像表達式,組表達式等等。
這個臨時的Java Source File是被動態編譯的,編譯器使用的是JDK中用來執行應用程序的編譯器(編譯器 class)。
如果 tools.jar不在classpath中,編譯過程將采用javac.exe來進行后臺編譯。
編譯后所得的字節碼保存在JasperReport類中,用來在執行期裝填數據(filling the report with data)和給表達式賦值(evaluate various report expression)。
JasperCompileManager
Class net.sf.jasper.engine.JasperCompileManager
這是一個上面提到的與編譯有關的類。利用它提供的一些方法,你將有能力編譯從本地硬盤或一個Input Stream 中獲取 XML report;還可以通過傳給 JasperCompileManager 一個JasperDesign類,來對內存中的report 設計進行編譯,功能很強大。
陳胤捷Print
Class net.sf.jasper.engine.JasperPrint
當一個報表已經裝填好數據之后,這個文檔就以JasperPrint類的實例出現。
這個類可以直接用JasperReport內置viewer進行查看,也可以序列化到硬盤以備后用,或者發送到網上去。
這個類的實例是報表裝填過程后的產物,它可以被JasperReport類庫中的導出方法導出成各種流行的格式,如PDF,HTML,XML等等。
JRDataSource
Interface net.sf.jasper.engine.JRDataSource
這個類與報表的數據源有關。只要能夠恰當的實現它的一些接口,用戶就可以在報表中使用各種數據源。
在報表裝填的時候由報表引擎負責對數據進行解釋和獲取。
當報表裝填的時候,報表引擎都會在后臺生成或提供一個該接口的實例。該實例是一個JRDataSource的缺省實現,因為很多報表數據都來源于關系數據庫,所以JasperReport缺省包含了java.SQLResultSet對象的實現。
這個類非常有用,既可用來包裹(wrap)結果集(裝填報表、且已經載入),也可被報表引擎用來包裹數據(通過Java數據庫連接執行查詢后所獲)。
顧名思義,這個類用于包裹java.swing.table.TableModel類中的數據,它也是實現了JRDataSource接口,用于在Java Swing程序中使用已經被載入到table中的數據來生成報表。
JREmptyDataSource
Class net.sf.jasper.engine.JREmptyDataSource
這是JRDataSouce接口的最簡單實現,這個類用在不需要顯示數據源數據而從參數中獲取數據的報表中,或僅需要知道數據源中的實際行數(number of virtual rows)的報表中。
JasperReport自帶的例子,如fonts、images、shapes和unicode,使用這個類對報表進行裝填,來模擬沒有任何record的數據源。這時所有的field都為null。
例如:
JasperRunManager.runReportToPdfFile(fileName, null, new JREmptyDataSource());
JasperFillManager
Class net.sf.jasper.engine.JasperFillManager
這個類用來實現報表的數據裝填。這個類提供了很多方法來接受各種類型的report 設計可以是一個對象,一個文件,或一個輸入流。它的輸出結果也是多樣的:file,Object,output Stream。
report的裝填引擎需要接收一個可以從中獲取數據和value的數據源作為報表參數。參數值(Parameters value)通常使用Java.util.Map來提供,里面包含的KEY是報表的參數名。
數據源可以通過兩種方式提供,這取決于你的解決方案。
通常情況下,用戶應該提供一個JRDataSource對象,例如我前面提到的那些。
但是大多數的報表都是采用關系數據庫中的值來裝填數據,所以JasperReport擁有一個內置的缺省行為,讓用戶在報表設計的時候提供一個SQL查詢。
在運行期,這個查詢將被執行以用來從數據庫中獲取要裝填的數據。在這種情況下,JasperReport僅需要一個java.sql.Connection對象來取代通常的數據對象。JasperReport需要這個連接對象來連接數據庫管理系統并執行查詢操作。
在查詢結束之后,JasperReport將自動生成一個JRResultSetDataSource,并將它返回給報表裝填過程。
JRAbstractScriptlet
Class net.sf.jasper.engine.JRAbstractScriptlet
這個類同樣用于報表裝填期間,用戶可以自己定義一些代碼,并由報表引擎在裝填過程中執行。這些用戶代碼可以處理報表數據操作,或在一些定義好的時刻執行,例如page,列,或組的分割處。
JRDefaultScriptlet
Class net.sf.jasper.engine.JRDefaultScriptlet
這是一個非常方便的JRAbstractScriptlet的子類。通常情況下你應該選擇繼承這個類。
JasperPrintManager
Class net.sf.jasper.engine.JasperPrintManager
這個類用戶提供打印方法,用戶可以將整個文檔或部分文檔傳遞給它,也可以選擇是否顯示打印Dialog,這在他的API文檔中可以找到,這里不再贅述。
JasperExportManager
Class net.sf.jasper.engine.JasperExportManager
顧名思義,這個類負責文檔的導出。這個類的具體信息詳見API文檔。非常明顯和清楚,沒什么好解釋的,Just use it即可。
JasperRunManager
Class net.SFjasper.engine.JasperRunManager
有時,我們僅僅需要構造一個流行的文檔格式,例如PDF,或HTML,而不需要將裝填過程后生成的JasperPrint對象保存到硬盤或其他中間媒體上。這時,可以使用這個類來直接將裝填過程生成的文檔導出到所需的格式。
JRViewer
Class net.sf.jasper.view.JRViewer
這是一個基于Swing的應用程序,你可以將它視為一個獨立組件,用來進行打印預覽。用戶可以繼承這個類,來構造滿足自身要求的預覽程序。
JasperViewer
Class net.sf.jasper.view.JasperViewer
這個類更像是使用JRViewer的教學組件,它演示了一個Swing應用程序如何裝載并顯示報表。
JasperDesignViewer
Class net.sf.jasper.view.JasperDesignViewer
這個類用于報表的設計期間,用來預覽報表模版。它僅作為一個開發工具存在于類庫中。
JRLoader
Class net.sf.jasper.engine.util.JRLoader
裝載器用于報表生成的各個主要階段,如編譯,裝填等等。用戶和引擎都可以利用這個類來裝載所需的序列化對象如file,URLs,intput stream等等。這個類最令人感興趣的函數當屬loadOnjectFromLocation(String location)。當用戶使用這個類從指定地點裝載對象的時候,該函數將首先將location解釋為一個合法的URL,如果解析失敗,函數將認為所提供的location是硬盤上的一個文件名,并將試圖讀取它。如果在指定地點沒找到文件,它將通過CLASSPATH定位一個相應于該location的資源,所有努力失敗之后,將拋出異常。
過程
這一節我們將看到對你的XML報表設計進行分析,編譯,裝填數據,預覽結果和導出到其他格式的過程。
第一步
XML解析
JasperReport使用SAX2.0 API對XML文件進行解析。然而,這并不是必須的,用戶可以在執行其自行決定使用哪一種XML解析器。
JasperReport使用org.xml.sax.helpers.XMLReaderFactory類的createXMLReader()來獲得解析器實例。在這種情況下,就像在SAX2.0文檔中說的那樣,在運行期,把Java系統屬性org.XMLsax.driver(這是屬性的key)的值(value)設定為SAX driver類的全限定名是必要的。用戶可以通過兩種方法做到這一點,我稍后將解釋全部兩種方法。如果你想使用不同的SAX2.0XML解析器,你需要指定相應的解析器類的名字。
設置系統屬性的第一種方法是在你啟動Java虛擬機的時候,在命令行使用-D開關:java ?Dorg.xml.sax.driver=org.apache.serces.parsers.SAXParser mySAXApp sample.xml
在JasperReport提供的所有例子中,都采用ANT構建工具來執行不同的任務。我們通過使用內置的 task中的元素來提供這一系統屬性:
第二種設置系統屬性的方法是使用java.lang.System.setProperty(String key, String value)
System.setProperty(“org.XMLsax.driver”,” org.apachexerces.parsers.SAXParser”);
Jsp/compile.jsp和web-inf/class/servlets/CompileServlet.java文件提供了這方面的例子。
注:對于第二種方法,我要說些題外話。有關于JavaOS的系統屬性(我們可以通過System.out.println(System.getProperty(“PropertyKey”)來查看),可以在運行期像上面說所得那樣用System.setProperty(“propertyKey”,”propertyValue”);來進行設置。但是一旦JVM已經啟動之后,其內建的系統屬性,如user.dir,就不能再被更改。奇怪的是我們仍可以用System.setProperty()方法對其進行設置,而在用System.out.println(System.getProperty())方法進行查看的時候發現,其值已經更改為我們設置的值,但事實上我們設置的值不會起任何作用。所以對于內置的屬性,我們只能通過-D開關在JavaOS執行之前進行設置。對于org.XML.sax.driver,由于它不是系統內建屬性,所以仍然可以在JVM啟動之后加以設置。更詳細的信息可以參考王森的〈Java深度歷險〉。
第二步
編譯報表設計(Report Designs)
為了生成一個報表,用戶需要首先生成報表的設計(report’s 設計),生成方法或采用直接編輯XML文件,或通過程序生成一個net.sf.jasper.engine.design.JasperDesign對象。本文中,我將主要采用編輯XML文件的方法,因為這種方法在目前是使用JasperReport類庫的最好的方法,并且我們有機會更好的了解類庫的行為。
先前提到過,XML報表設計是JasperReport用來生成報表的初級材料(raw meterial)。這是因為XML中的內容需要被編譯并載入到JasperDesign對象中,這些對象將在報表引擎向其中填入數據之前經過編譯過程。
注意:大多數時候,報表的編譯被劃歸為開發時期的工作。你需要編譯你的應用程序報表設計,就像你編譯你的Java源文件一樣。在部署的時候,你必須將編譯好的報表,連同應用程序一起安裝到要部署的平臺上去。這是因為在大多數情況下報表設計都是靜態的,很少用應用程序需要提供給用戶在執行期編譯的,需要動態生成的報表。
報表編譯過程的主要目的是生成并裝載含有所有報表表達式(report expression)的類的字節碼。這個動態生成的類將會被用來在裝填數據,并給所有報表表達式求值(evaluate)的時候使用。具體例子是,如果你用ireport生成一個報表名字叫SimpleSheetTest,它的XML設計文件名叫SimpleSheetTest.jrxml,同時和它在同一目錄下IReport會自動生成一個文件名為SimpleSheetTest.java,里面主要是一些報表元素,如Field,Parameters,Variables的定義,以及一些求值表達式。當然,像上面提到的,這個文件在你直接使用JasperReport API的時候是看不到的,因為它是在執行期生成的一個Class。要想看到它的辦法是:在IDE(JBuilder,Eclipse)中單步執行程序,在報表打印的階段,你將能跟蹤到這個類,它的名字就是“你的報表名.java”,按上面的例子就是SimpleSheetTest.java,這和ireport是一致的。當然也可以像下面說的那樣,到生成這個類的臨時目錄里找到它。
在這個類生成過程之前,JasperReport引擎需要驗證報表設計的一致性(consistency),哪怕存在一處驗證檢查失敗都不會繼續運行下面的工作。在下面的章節,我將會展示報表設計驗證成功之后的狀況。
對于這個包含了所有報表表達式(report expressions)的類的字節碼,我們至少需要關心三個方面的內容:
臨時工作目錄(temporary workingl directory)
JAVA編譯器的使用:
為了能夠編譯Java源文件,這個文件必須被創建并且被保存到磁盤上。Java編譯過程的輸出是一個.class文件,這個包含所有報表表達式的類在這個工作目錄里被創建并編譯,這也是為什么JasperReport需要訪問這個臨時目錄的原因。當報表的編譯過程結束之后,這些臨時的類文件將被自動刪除,而生成的字節碼將保存在net.sf.jasper.engine.JasperReport對象中。如果需要的話,這個類可以將自己序列化(serialized itself)并保存到磁盤上。這就是IReport的做法。
缺省情況下,這個臨時工作目錄就是啟動JavaOS時的當前目錄,這卻取決于JVM的系統屬性user.dir。通過更改系統屬性jasper.report.compile.temp,用戶可以很容易更改這個工作目錄。在Web環境下,特別是當你不想讓含有啟動Web Server的批處理文件的目錄和報表編譯過程的臨時工作目錄混在一起的時候,修改這個屬性就可以了。
上面提到的第二個方面涉及用來編譯報表表達式類的JAVA編譯器。首先,報表引擎將試圖使用sun.tools.javac.Main類來編譯Java源文件。這個類包含在tools.jar中,當且僅當這個jar文件在JDK安裝目錄下的bin/目錄中,或在classpath中時,sun.tools.javac.Main才能正常使用。
如果JasperReport不能成功裝載sun.tools.javac.Main文件,程序將動態執行java編譯過程,就像我們通常用命令行那樣,使用JDK安裝目錄下的bin/目錄下的javac.exe。這就是為什么將JDK安裝目錄/lib/下的tools.jar文件copy到JasperReport工程的lib/目錄下是一個可選的操作(optional operation)。如果tools.jar不在classpath中,JasperReport將顯示錯誤信息并繼續上面提到的操作。
當編譯Java源文件的時候,最重要的事情莫過于CLASSPATH。如果JAVA編譯器不能在指定的classpath中找到它試圖編譯的所有相關類的源文件,則整個過程將失敗并停止,錯誤信息將在控制臺顯示出來。同樣的事情也將發生在JasperReport試圖編譯報表表達式類的時候。所以,在runtime為編譯過程提供正確的classpath是非常重要的。例如,我們我們需要確認在classpath中,我們提供了在報表表達式中可能用到的類(custom class)。
在這個方面也有一個缺省的行為。如果沒有為編譯report class特殊指定classpath,引擎將會使用系統屬性java.class.path的值來確定當前的JavaOS classpath。如果你指定了系統屬性jasper.reports.compile.class.path的值,你可以用你定義的CLASSPATH來覆蓋缺省行為。
大多數情況下,編譯一個report只需要簡單的調用JasperReport類庫中的JasperCompileManager.compileReport(myXmlFileName);即可。調用之后將生成編譯好的report design并存儲在.jasper文件中,這個文件將會保存在和提供XML report 設計文件相同的目錄中。
第三步
Report Design 預覽
JasperReport類庫并沒有提供高級的GUI工具來輔助進行設計工作。然而,JasperReport本身提供了一個很有用的可視化組件來幫助報表設計者在編譯的時候預覽報表設計(其實不如直接用IReport方便)。
net.sf.jasper.view.JasperDesigner是一個基于Swing的Java應用程序,它可以載入并顯示XML形式或編譯后的報表設計。盡管它不是一個復雜的GUI應用程序,缺乏像拖拽可視化報表元素這樣的高級功能,但是它仍然是一個有用的工具(instrument)。所有JasperReport工程提供的例子都利用了這個報表查看器(report viewer)。
如果你已經安裝了ANT(別告訴我你不知道什么是ANT),想要查看一個簡單的報表設計(JasperReport工程所帶例子),你只需要到相應的文件夾下輸入如下命令:
〉ant viewDesignXML 或者 〉ant viewDesign
如果你沒安裝ANT,要達到上面的效果就不是很容易,因為JasperReport本身需要一些其他輔助的jar包(在JasperReport安裝目錄/天秤座下),在運行的時候,你需要把這些jar包都包含到你的classpath里面,并且正確設計系統屬性,如上面提到的org.XMLsax.driver。我可以展示一下在Windows下的例子:
>java -classpath ./;../../../lib/commons-digester.jar;
../../../lib/commons-beanutils.jar;../../../lib/commons-collections.jar;
../../../lib/xerces.jar;../../../lib/jasperreports.jar
-Dorg.XMLsax.driver=org.apache.xerces.parsers.SAXParser
dori.jasper.view.JasperDesignViewer -XML -FFirstJasper.xml
很麻煩吧?還是趕快弄個ANT吧。下面是預覽之后的結果(其實用IReport更好)
第四步
報表裝填(Filling Report)
報表裝填(report filling)過程是JasperReport library最重要的功能。它體現了這個軟件最主要的目的(main objective),因為這一過程可以自由的操作數據集(data set),以便可以產生高質量的文檔。有3種材料需要裝填過程中作為輸入提供給JasperReport:
report 設計(reportl templet)
參數(parameters)l
數據源(datal source)
這一過程的輸出通常是一個單一的最終要被查看,打印或導出到其他格式的文檔。
要進行這一過程,我們需要采用net.sf.jasper.engine.JasperFillManager類。這個類提供了一些方法來讓我們裝填報表設計(report design),report design的來源可以是本地磁盤,輸入流,或者直接就是一個已存在于內存中的net.sf.jasper.engine.JasperReport類。輸出的產生是于輸入類型相對應的,也就是說,如果JasperFillManager接到一個report 設計的文件名,裝填結束后生成的report將會是一個放在磁盤上的文件;如果JasperFillManager收到的是一個輸入流,則生成的report將會被寫道一個輸出流中。
有些時候,這些JasperFillManager提供的方法不能滿足某些特定的應用的要求,例如可能有人希望他的report design被作為從CLASSPATH中得到的資源,并且輸出的報表作為一個文件存放在一個指定的磁盤目錄下。遇到這種情況時,開發人員需要考慮在將報表設計傳遞給報表裝填過程之前,用net.sf.jasper.engine.util.JRLoader類來裝載report 設計對象。這樣,他們就能獲得像報表名這樣的report design屬性,于是開發者就能生成最終文檔的名字(construct the name of the resulting document),并將它存放到所需的位置上。
在現實中,有許多報表裝填的情境(scenarios),而裝填管理器僅試圖覆蓋其中最常被使用到的部分。然而對于想要自己定制裝填過程的人來說,只要采用上面所說的方法,任何開發者都可以達到滿意的結果。
報表參數通常作為java.util.Map的value提供給裝填管理器,參數名為其鍵值(key)。
作為裝填過程所需的第三種資源?數據源,有如下兩種情況:
通常,引擎需要處理net.sf.jasper.engine.JRDataSource接口的一個實例,通過這個實例,引擎可以在裝填過程中獲取所需數據。JasperFillManager提供的方法支持所有的JRDataSource對象(這是一個Interface,上面一章提到過它的常用實現)。
然而,這個管理器還提供一些接受java.SQLConnection對象作為參數的方法集,來取代所需的數據源對象。這是因為在很多情況下,報表生成所需的數據都來源于某個關系型數據庫中的表(table)。
在報表中,用戶可以提供SQL查詢語句來從數據庫中取回報表數據(report data)。在執行期,engine唯一需要做的是獲得Java數據庫連接connection對象,并使用它來連接想要連接的數據庫,執行SQL查詢并取回報表數據。在后臺,引擎將使用一個特殊的JRDataSource對象,但是它對于調用它的程序來說是透明的。
JasperReport工程提供了相關的例子,它們采用HSQL數據庫服務器(在工程文件中,有一個相應的文件夾),要運行這些例子你需要首先啟動該服務器,方法是:在/demo/hsqldb目錄下輸入如下命令:>ant 或者 >ant runServer
沒裝ANT就麻煩點:>java -CLASSPATH ./;../../lib/hsqldb.jar org.hsqldb.Server
一下代碼片斷顯示了query例子是如何裝填數據的:
//Preparing parameters
Map parameters = new HashMap();
parameters.put("ReportTitle", "Address Report");
parameters.put("FilterClause", "'Boston', '芝加哥', '奧斯陸'");
parameters.put("OrderClause", "City");
//Invoking the filling process
JasperFillManager.fillReportToFile(fileName, parameters, getConnection());
第五步
查看報表(Viewing Reports)
報表填充階段的輸出通常是一個JasperPrint對象,如果把它保存在磁盤上,通常以一個.jrprint文件的形式存在。JasperReport擁有一個內置的查看器,用來查看用內置的XML導出器(XML exporter)獲得的XML格式的報表文件。這個查看器就是以前提到過的net.sf.jasper.niew.JRViewer?一個基于Swing的應用程序組件,用戶可以通過繼承這個類來定制自己所需的查看器。JasperReport工程中自帶的例子webapp中,你可以閱讀JRViewerPlus類的代碼來獲取進一步內容。
注意:JasperViewer更像是一個教人們如何使用JRViewer組件的演示程序,這里要注意一點,當你調用JasperViewer的viewReport()方法來顯示報表時,如果你關閉了預覽Frame,整個應用程序將會隨之結束,因為這個函數最后調用了System.exit(0);你可以通過繼承這個類,并重新在你的Viewer里注冊java.awt.event.WindowListener來避免這一情況的發生。
第六步
打印報表
JasperReport類庫的主要目標,就是生成可打印的文檔。而且多數應用程序生成的報表都是需要落實(或打印)到紙張上。我們可以用net.sf.jasper.engine.JasperPrintManager來打印JasperReport生成的文檔。當然,報表也同樣可以在被導出到其他格式如PDF,HTML之后再被打印。通過JasperPrintManager提供的方法,我們可以打印整個文檔,打印單個文檔或打印某一范圍內的文檔,可以顯示打印對話框也可以不顯示。下面的例子演示了不顯示對話框,打印整個文檔的方法:JasperPrintManager.printReport(myReport,false);
這個例子顯示了如何打印5-11頁的文檔,同時顯示打印對話框:net.sf.jasper.engine.JasperPrintManager.printPages(myReport,4,10,true);
第七步
導出報表
在一些應用程序環境下,將JasperReport生成的文檔從其特有的格式導出到其他更為流行的格式如PDF,HTML是非常有用的。這樣一來,其他人就可以在沒有安裝JasperReport的情況下查看這些報表,特別是當這些文檔要通過網絡發送出去的時候。
JasperReport提供了JasperExportManager類來支持此項功能。這項功能將會在以后不斷加入對新的格式的支持。HTML和XML類型的文檔,下面是導出的代碼片斷:JasperExportManager.exortReportToHtmlFile(myReport);
注意:想要將自己的報表導出到其他格式的用戶,需要實現JRExporter的接口,或繼承相應的JRAbstractExporter類。
第八步
對象的載入和保存
當使用JasperReport的時候,你經常會與序列化的對象,如以編譯的報表設計,或已生成的報表打交道。有時,你需要手動載入從不同的source如input stream或你用類庫核心功能(lib’s core functionality)產生的序列化類。JasperReport提供了兩個特殊的工具類來提供上述操作的能力,這些類通常供報表引擎自己使用:
net.sf.jasper.engine.util.JRLoader
net.sf.jasper.engine.util.JRSaver
第一個類提供了一些方法讓我們能夠從不同類型的數據源如文件,URL,input stream和CLASSPATH里面獲取序列化對象。最令人感興趣的方法是loadObjectFormLocation(String)。它已經在上一章中介紹過了,這里不再贅述。
與上面的對象載入工具相反的部分是JRSaver類,它可以幫助程序員將自己的類序列化之后存放到本地磁盤或通過Output Stream發送到網絡上去。
有時,開發人員可能想要載入已經生成好的report,或最終的已經被導出到XML格式的JasperReport文檔,這與上面所說的直接load序列化對象有所不同。這時,我們需要載入的是將載入的XML內容進行編譯,并生成JasperPrint對象,而并非僅僅是載入序列化對象。這時,我們可以通過net.sf.jasper.engine.xml.JRPrintXmlLoader類的一些靜態方法,通過編譯從XML文件中讀取的內容構建出一個位于內存中的文檔對象。
設計
(Report Designs)
報表設計體現了一個模版,JasperReport引擎利用這個模版將同臺生成的內容傳遞給打印機,屏幕或Web。存儲在數據庫中的數據在報表裝填的過程過被組織起來,根據已有的報表設計來獲得可以進行打印的,面向頁面的(page oriented)文檔。
總而言之,一個報表設計包含了所有的結構相關信息和將數據提供給報表所涉及的各個方面。這些信息涉及將在文檔中顯示出來的各種text或圖像元素的位置和內容,自定義計算(custom calculation),數據組織,報表生成時的數據操作,等等。
報表設計通常都定義在一個擁有特殊格式的XML文檔中,并且在被填充數據之前要經歷JasperReport的編譯過程,有關于這個XML文檔的詳細信息我們將在以后說明。然而JasperReport也允許用戶通過JasperReport提供的API構造in-memory報表對象,例程noxml設計就是很好的例子,但是我們通常不這么用。
DTD
DTD Reference
當使用XML文件進行報表設計的時候,JasperReport將使用內置的DTD文件來驗證其受到的XML內容的有效性。如果XML驗證通過,則說明所提供的報表設計符合JasperReport所需要的XML結構和語法規則,其引擎能夠生成經過編譯的report design。
有效的XML文檔總是在驗證時指向JasperReport的內部DTD文件。如果沒有提供DTD文檔的引用,報表的編譯過程將會突然結束。這對所有人來說都是一個負擔,因為DTD引用通常是相同的,并且這些引用可能會簡單的被從以前的報表設計中copy過來。在一開始,你需要將這個引用從給定的例子中copy過來。
正如以前說的一樣,報表引擎僅能識別指向其內部DTD文件的的引用。你不能隨便從類庫的源文件中將那些DTD文件copy到別的地方,再在你的報表設計文件中文件中指向你copy的那些DTD文件。如果你想那樣做的話,你將需要調整類庫中某些類,包括net.sf.jasper.engine.XMLJRXmlDigester類的某些代碼。如果你遇到像引擎無法找到其內部的DTD文件而導致的無法載入資源的問題,請確定你已經在使用外部DTD文件之前排除了所有可能發生的情況。遇到這樣的問題是不太可能的,因為資源載入機制會隨著時間不斷改進。
XML
XML 編碼
當要生成不同語言的XML報表設計的時候,在XMl文件的首部的編碼屬性需要特別關注一下。缺省情況下,如果這個屬性的值沒被訂制,則XML解析器將會使用“UTF-8”作為XML文件的編碼格式。這一點是非常重要的,因為報表設計通常包含了靜態的本地化text。對于大多數西歐語言來說,ISO-8859-1編碼,也就是我們常說的LATIN1將會很好的處理如法語中重音符號的顯示問題。
在編輯XML文件的時候,要找到某種特殊語言的編碼類型,你可以查看XML document.FIXME
報表屬性
我們上面已經看到,斯XML報表設計的根元素。這一節我將介紹報表設計對象的Property的細節以及這些屬性所對應的XML attributes(為避免混淆,我將不提供Property和Attribute的中文而直接使用英文)。
Report Name
每一個報表都必須有一個名字。這個名字是相當重要的,因為類庫需要它來生成文件,尤其是當編譯,裝填,導出報表的默認行為被使用的時候,這個名的作用更為重要。這個名字是以元素的name attribute的形式提供的,并且是強制必須填寫。
Column Count(列數)
JasperReport允許生成每頁的列數超過一列的報表。默認情況下,報表引擎生成每頁一列的報表。
Print Order(打印順序)
對于擁有超過一列的報表,為其提供列將被以什么順序填充是很重要的。你可以使用的printOrder attribute來進行設置。有如下兩種情況:
Verticaln Filling:這個選項將導致列是自頂向下被填充(printOrder=”Vertical”)
n Horzontal:列將自左向右被填充(printOrder=”Horizontal”)
缺省設置將是printOrder=Vertical
Page Size(頁面大小)
有兩個attribute是用來提供要生成的文檔大小的:pageWidth,pageHeight。像所有其他顯示元素位置和尺寸的attribute一樣,這兩個attribute是以像素為單位的。JasperReport采用Java默認的每英寸72點的設置。這意味著pageWidth=”595”將大約是8.26英寸,這大概是A4紙的尺寸。
默認的紙張大小是A4紙:pageWidth=”595” pageHeight=”842”
Page Orientation(默認設置為Portrait)
orientation屬性用來設置文檔打印格式是“Portrait”還是“Landscape”。JasperReport允許用戶在從“Portrait”切換到“Landscape”的時候調整液面的寬度和高度。我們先看一個例子:我們假定要生成一個A4紙的報表,采用“Protrait”格式。
pageWidth=”595” pageHeight=”842” orientation=”Portrait”
如果我們決定用A4紙的“Landscape”布局,首先要調整相應的頁面寬度和高度:
pageWidth=”842” pageHeight=”595” orientation=”Landscape”
這是因為JasperReport需要確切知道它所要繪制的報表頁的寬度和高度,而不只看我們提供的orientation屬性,至少在報表裝填的時候是這樣。orientation屬性僅在報表打印時有用,來通知打印機或某些exporters頁面的orientation設置。
Page Margins(頁邊距)
一旦頁面大小確定下來,用戶就可以在生成報表的時候設定報表的邊距。有四個屬性來完成這項工作:topMargin,LeftMargin,bottomMargin和rightMargin。缺省的設置是上下邊距20像素,左右邊距20像素。
Column Size and Spacing(列寬和列間距)
一個報表可能含有多列,我們可以通過上面提到的columnCount屬性得到報表列數。JasperReport需要知道列的寬度和列間距的大小。有兩個屬性用于這項工作:columnWidth和columnSpacing。當我們對報表設計進行編譯的時候,編譯器會對這項設置進行有效性檢查(validation check)--看列的寬度和列間距是否符合給定的頁面寬度和頁邊距。因為缺省的列數為一,所以缺省的列間距為0像素,并且缺省的列寬等于頁面寬度減去左右邊距所得的值。在上面A4的例子中,列寬即為555像素。
Empty Data Source Behavior
有時我們提供給我們的報表的數據源可能會沒有任何record。whenNoDataType屬性可以讓你選擇當所提供的數據源中沒有數據的時候入和察看生成的報表。如下有三種不同的可能性,你可以任選其一:
l Empty Document:生成的報表不含有頁面(no page in it)。當你試圖裝載這樣的文檔(whenNoDataType=”NoPages”)的時候,Viewer 可能會拋出一個錯誤。
Blankl page:表示生成的報表將僅含有一個空白頁。(whenNoDataType=”BlankPage”)
All sectionsl displayed:除了detail部分的其他部分將在生成的文檔中顯示出來。(whenNoDataType=”AllSectionNoDetail”)。
缺省的設置是whenNoDataType=”NoPages”。
Title and Summary Sections Placement(標題和摘要的放置)
如果你想讓title部分和summary部分在單獨的一頁里顯示,你所需要做的事情就是讓下面的一個或兩個屬性的值為“true”:isTitleNewPage,isSummaryNewPage。這兩個屬性缺省情況下為false。
注意:即使你選擇了在最后一頁的剩余部分顯示summary,如果列數超過一列,并且第二列已經在最后一頁出現的時候(沒試過,等有機會試驗一下),新的一頁將會被自動生成。
Scriptlet Class
scirptletClass屬性用于設置用于當前報表的scriptlet類的名字。在以后我會對Scriptlet進行詳細討論。如果你沒為這個屬性提供任何值,報表引擎將會使用net.sf.jasper.engine.JRDefaultScriptlet的實例。
數據
(Report Data)
當我們談到報表裝填過程的時候,有三樣東西需要作為輸入提供給報表引擎:報表設計(report design),參數值(parameter values)和報表的數據源(data source)。
在先前的章節,我們已經看到了有關報表設計的某些方面,像你用其他報表工具所希望的一樣,這些數據將會根據報表設計中的定義的模版(template)被組織起來,并被用來生成準備打印的、面向頁面的文檔。
表達式
表達式(expressions)
表達式是JasperReport的一個非常有用的特性。他們可以用來聲明執行各種執行各種計算(calculations)的報表變量(report variables),進行報表的數據組織,定制報表文本字段(text field)的內容或者進一步定制報表對象的appearance。
所有報表表達式基本上都是Java表達式,他們可以以特殊的語法引用報表參數(parameter),報表字段(field)和報表變量(variable)。在XML報表設計中,有一些用于定義表達式的元素:,,,,,等等
因為所有的JasperReport表達式都是真正(real)的Java表達式,只要你用完整的類名(包括包名)來引用這些表達式,你就可以在任何class中使用他們。當你編譯報表和裝填數據的時候,你應該確定你在報表表達式中使用的類已經寫入了classpath。
報表參數引用是通過$P{}序列引入的,例如:
$P
這個例子假定我們在報表設計中有一個名為ReportTitle的報表參數,這個參數是一個java.lang.String類。當報表進行裝填的時候,文本字段將會顯示這個參數的值。
為了在一個表達式中使用報表字段,字段名必須放在$F{}的括號中。例如,如果我們想要在一個文本字段中顯示兩個數據源字段的連接值(concatenated values),我們可以定義如下表達式:
$F + “ “ + $F
表達式可以更加復雜:
$F + “ “ + $F + “ was hired on “ +
(new SimpleDateFormat(“MM/DD/YYYY”)).format($F{HireDate)) + “.”
正如你所看到的一樣,參數、字段和變量引用都是通過用JasperReport的特殊語法從一個真正地Java對象中引入的。(實際上是一個JREvaluator對象,該對象是在運行其生成的動態對象,并不能在本地磁盤上見到它的身影,不過如果你使用ireport的話,你就可以在生成報表文件的同一目錄下看到它的本來面目)
參數
參數(Parameters)
Parameters是傳遞給報表裝填操作的對象引用。這些參數主要作用于把那些不能從報表數據源中獲得的數據傳給報表引擎。例如,我們可能要把執行報表裝填過程的用戶名字專遞給報表引擎,如果我們想讓它顯示在報表上或者在我們想在報表的title上動態的改變它,我們就可以以參數的形式傳給報表引擎。
我們可以用如下的方式定義參數:
這里所提供的報表參數值可以被用到各種報表表達式中,在報表SQL查詢中,甚至可以用到報表的scriptlet類里。下面是構成參數定義的全部組件(XML元素):
參數名
name屬性是一個強制屬性。JasperReport的命名習慣和Java語言的命名習慣是類似的,這意味著參數名應該是一個單詞,其中不含特殊字符(如分號)。
參數類型
報表參數的第二個強制屬性是提供參數值的類型名。這個類型屬性可以使任意的值,只要這個類型的名字在報表編譯期和裝填期能在classpath中找到即可。
Prompting for Parameter values
在GUI引用程序中,建立一個報表參數集,讓用戶在執行裝填過程之前輸入某些應用程序需要用戶輸入的報表參數是很有用的。可選參數isForPropting參數用來聲明是否顯示提示信息讓用戶輸入某些參數。下面的例子中,我聲明了一個文本參數,當需要用戶輸入參數值的時候,這個文本參數用來在一個已定制的對話框中描述需要用戶輸入什么樣的參數。
Please type here the report comments if any
]]>
注意:相信大家都知道表示“內容”將不被XML解析器解析,也就是說,你可以在“內容”里加入XML的特殊字符,如>,<等等。
參數的默認值(parameter default value)
通常參數值都是使用java.util.Map對象傳給裝填過程的,其中參數名作為Key。這樣,你就不用每次都為每個參數提供一個value了?可以批量放到Map對象里一起傳給裝填管理器。如果你沒有為參數提供一個value,引擎就認為它是null。但是如果你為它提供了一個默認值,則引擎將在你沒提供這個參數值的情況下使用這個默認值。如果你在裝填時沒提供數據,下面的java.util.Date將在裝填是被引擎使用來表示當天日期:
new java.util.Date()
在參數的默認表達式中,我們可以使用這個與定義的報表參數。
內置的報表參數
每一個報表設計中都含有一些與定義的報表參數,這些內置的參數的描述如下:
REPORT_PARAMETERS_MAP
這是一個內置的參數,這個參數總是指向一個java.util.Map對象,該對象保存了用戶調用報表裝填過程時傳遞給報表引擎的用戶定的參數。
REPORT_CONNECTION
這個報表參數指向一個java.SQLConnection對象,這個對象被提供給報表引擎用來通過Java數據庫連接來執行SQL報表查詢。將master報表使用的JDBC Connection對象傳遞給subreport是非常有用的,有關這方面信息請查看subreport例子
REPORT_DATASOURCE
在報表裝填的時候,我們可以或者直接由應用程序中提供,或由報表引擎從所提供的JDBC Connection在后臺create而獲得一個數據源。這個內置的參數允許我們在報表表達式中或scriptlet中訪問報表數據源,而不論我們為什么要這么做。
REPORT_SCRIPTLET
即使報表不使用scriptlet,這個內置的參數仍將指向一個net.sf.jasper.engine.JRAbstracStriptlet實例,該實例實際是一個net.sf.jasper.engine.JRDefaultScriptlet對象。
但是當使用scriptlet時,報表裝填過程所生成的這個指向scriptlet類實例的引用允許我們調用其中的某些特殊函數,使用或控制scriptlet對象在裝填過程中已經準備好的數據。在scriptlet例子中你可以看到更詳細的使用過程。
數據源
Data Source
在進行報表裝填的時候,JasperReport引擎迭代的從用戶提供的數據源中提取record,并根據報表設計所提供的模版生成報表的各個部分(section)。通常情況下,引擎需要接收一個net.sf.jasper.engine.JRDataSource對象作為報表數據源。但是像我們即將看到的那樣,當報表數據存儲于關系型數據庫的時候,JasperReport有了讓用戶提供一個Java數據庫連接鏈接對象來替代通常的數據源對象的特性。JRDataSource接口非常簡單,如果我們想要實現它只需要實現下面兩個方法:
public Boolean next() throw JRException;
public Object getFieldValue(JRField jrField) throw JRException
在報表裝填的時候,next()方法將被報表引擎調用,迭代的從數據源中獲取數據。第二個方法用來為每個在當前數據源記錄(data source record)中的報表字段(report field)提供value。
應當知道,從數據源取得數據的唯一方法是使用report field。一個數據源對象更像是一個二維表,表中含有數據。這個二維表的行是一條一條的record,而每一列都映射為一個report field。所以我們可以在report表達式中使用數據源。JasperReport提供了一些缺省的JRDataSource實現,我們來具體看一下:
這是一個非常有用的缺省實現,因為他外覆(wrap)了java.SQLResultSet對象。由于多數報表的生成都采用關系數據庫中存儲的數據,所以這個類是被使用得最為廣泛的數據源對象。然而在以下的兩種情況下您可以不必在裝填過程中自己生成這個對象:
如果你選擇在你的報表中用SQL查詢來獲得在關系數據庫的某個table中的數據,報表引擎將會通過執行給定的SQL查詢并且將返回的java.sql.ResultSet外覆為一個net.sf.jasper.engine.JRResultSetDataSource實例來執行這項操作。引擎唯一需要的是一個java.SQLConnection對象來執行查詢操作。這時你可以提供connection對象來作為通用數據源對象(usual data source object)。例子有:jasper,scriptlet,subreport和query。
當然你可以在應用程序中即JasperReport之外執行SQL查詢。這樣的話,你可以手動的外覆java.sql.ResultSet,再調用報表裝填過程之前實例化這個數據源對象。當使用這種類型的數據源的時候,你需要為在result set中的每一列生命一個report field。report field的名字和類型必須和列的名字和類型匹配。
Class net.sf.jasper.engine.JREcptyDataSource
這個類主要用于當生成報表的數據不是來自數據源,而是來自參數或重要的僅是數據源中virtual records的數量的時候。例子fonts,images,shapes和unicode都使用了這個類來裝填報表,來模擬數據源中沒有一條記錄,所有字段都為null的情況。
這個JRDataSource接口的缺省實現外覆了javax.swing.table.TableModel對象,它可以用在Java Swing應用程序中從已經顯示到屏幕上的table中得數據來生成報表。--我喜歡。
通常有兩種方法來使用這種數據源:
通常,為了要從中取得數據,你需要為javax.swing.table.TableModel對象的每一列生命一個report field。但是有些情況下會出現問題,比如report field的命名需要遵照Java命名規范來聲明變量,而table的列名則不需要。幸運的是,你仍然可以通過列的索引而不是它的名字來將report field與列進行映射。例如,一個列名為“Produce Description”不可能被映射到名為“Produce Description”的report field上,因為report field名中含有空格,這將引起一個編譯錯誤。但是如果你知道這個列示table model對象的第三列(index=2),那么你就可以命名相應的字段“COLUMN_2”并無誤地使用這一列的數據。例子有:datasource
Class net.sf.jasper.engine.data.JRBeanArrayDataSource
這個類外覆了一個JavaBeans數組,并且通過反射來獲取report field的值。在這種數據源中,一個javaBean對象描述了一條記錄。如果我們有一個名為“ProductDescription”的report field,在獲取這個字段的值的時候,程序將會試圖通過反射機制調用一個當前JavaBeans對象中]名為getProductDescription()的方法。對于boolean字段,當調用get前綴的屬性不能返回其屬性值的時候,程序將會試圖使用is前綴的方法來獲得屬性值。
Class net.sf.jasper.engine.data.JRBeanCollectionDataSource
這個類和上一個類非常類似,它也是使用反射機制和javaBean命名規范,但是它外覆了一個java.util.Collection對象而不是一個JavaBean對象數組。在datasource例子中你可以看到進一步的用法。
報表查詢
報表查詢(Report Query)
為了要為報表裝填數據,我們需要為報表引擎提供所需的數據,或者至少告訴它怎樣去獲取數據。JasperReport通常需要接受一個net.sf.jasper.engine.JRDataSource對象作為報表的數據源,同時作為更為強大的功能,JasperReport能直接用JDBC從關系數據庫中獲取數據。類庫允許用戶在他們的報表設計中提供SQL查詢以便可以自運行期從數據庫中提取數據。要做到這一點,你只需要在裝填的時候為裝填管理器的fillReport()方法提供一個java.sql.Connection而不是JRDataSource對象即可。
在報表中,可以使用元素來引入查詢。如果這個元素存在,則出現在報表參數聲明之后,報表field之前。
如下是一個SQL查詢的例子:
為了更好的定制從數據庫中取回的數據集(data set),一個重要的方面是在報表查詢字符串中報表參數的使用(use of report parameters)。在查詢中,這些參數可能會像動態過濾器(dynamic filter)一樣工作,它們用特殊的語法被引入進來為報表提供數據,很像report expression。
如下有兩種在查詢中的使用參數的方法:
1. 像通常的java.SQLPreparedStatement的參數那樣使用,用如下語法:
SELECT * FROM Orders WHERE OrderID <= $P ORDER BY ShipCountry
]]>
2. 有時,我們需要使用參數來動態更改SQL查詢的某些部分,或將整個SQL查詢作為參數提供給裝填過程。在這種情況下,語法稍微有些不同,向下面的例子,注意“!”
SELECT * FROM $P! ORDER BY $P!
]]>
在這個例子中,這個引入了參數值得特殊的語法確定了我們為這些參數所提供的值將會替代查詢中的參數引用($P!{}的內容)。這些參數將被傳給使用java.SQLPrepqredStatement對象的數據庫服務器。
事實上,報表引擎首先處理$P!{}參數引用,通過使用他們的值來獲取最重的SQL查詢,并且僅當這件事完成之后,引擎才會將剩下的普通的$P{}參數引用傳遞給usual IN parameters。--實際上就是嵌套查詢啦。
第二種用于SQL查詢的參數引用允許你在運行期傳遞整個SQL查詢語句:
$P!
注意:你不能在參數值中再加入參數引用,也就是說,參數引用不能嵌套使用。
更詳細的信息可以參看工程所帶的例程:jasper,subreport,scriptlet,webapp以及最有學習價值的query
參考資料 >