序言
一直以來,報表都是很多項目中一個重要的、不可獲取的組成部分。然而其復雜性和專業(yè)性又使得程序員不能夠也沒時間自己設計屬于目前手頭正在構(gòu)建的系統(tǒng)的報表模塊;即便設計來了又可能由于通用性等原因不能夠應用到下一個系統(tǒng)當中,這就導致了報表類庫/組件在市場上的火爆。典型的例子就是水晶報表,幾乎無處不在。還有一些專門處理中國式報表的組件也陸續(xù)出現(xiàn)在軟件市場上。然而遺憾的是,他們中的絕大多數(shù)都是要收費的--這也無可厚非,畢竟人都是要生存的。所以大多數(shù)預算不夠充裕的程序員都將目光轉(zhuǎn)向了開源軟件,而JasperReport就將是第一個進入他們視線中的佼佼者。
然而代碼開源不代表我們就可以大大方方的拿來就用了,人家的文檔也是要收費的,所以市面上有關于JasperReport的文檔雖然也不少,但大多數(shù)都集中在簡單應用和介紹基本操作的基礎之上。對于復雜的報表設計并不能提供良好的幫助。本文將火力集中在相對復雜的報表設計(不包括圖片和超連接等對辦公系統(tǒng)或過程沒有太大用途的頁面元素),交叉表的生成等高級報表設計方案。對于那些基本的操作則留給讀者自行體會,相信可以為各位解決一些實際問題。
JasperReport是JasperSoft公司的一款開源的報表解決方案。通過JasperReport,用戶就可以方便的定制、設計、生成項目所需要的各種報表。和JasperReport一樣,iReport也是Sourceforg上的開源項目。它的出現(xiàn),主要是為了降低JasperReport的使用難度,為用戶提供可視化的報表設計工具,目前iReport的版本號為1.2.7。

每個版本的iReport都會與最新的JasperReport相匹配的功能,并可以手動設置與用戶所使用的JasperReport兼容性,以便懷舊的用戶可以無障礙地使用最新的iReport:

限于篇幅,我不可能把iReport操作的每一個細節(jié)都記錄在本文中,而只能對涉及到的部分作出簡要介紹,剩下的諸如報表中的各個報表元素是什么,屬性都有什么;什么是iReport的字段(Filed),參數(shù)(parameter)和變量(variable)等等這些基本概念,如果想要深究細節(jié)的話有兩種選擇:1是尋找其官方文檔(要收費),網(wǎng)上流傳的文檔版本較低,但是也可以作為參考,如果想要我翻譯的JasperReport用戶文檔,可以給我發(fā)郵件。2是自己動手實踐一下吧。
做報表的目的就是顯示數(shù)據(jù),無論是簡單的查詢結(jié)果還是某些統(tǒng)計信息。所以我們第一步要做的就是設定iReport中的數(shù)據(jù)源或數(shù)據(jù)庫連接,以便于從數(shù)據(jù)庫中動態(tài)獲取數(shù)據(jù)。在菜單->Connection and Datasource里我們可以設定所要采用的數(shù)據(jù)提供方式。iReport為我們提供了豐富的選項:

通常情況下,我們一般有兩種選擇,一是選擇數(shù)據(jù)庫連結(jié)的數(shù)據(jù)源,通過這種方式,我們就可以直接在報表設計中寫入SQL查詢語句,讓報表在運行期自動獲取所需的數(shù)據(jù)來裝填報表而不需要做額外的工作。但這樣一來,就必然會損失一些程序的靈活性,比如查詢語句或數(shù)據(jù)庫連接需要修改的時候我們就不得不重新填入相應的內(nèi)容并編譯報表。所以我在工作中通常采用第二種方式,即用JavaBean的集合(Collection或Array)來充當數(shù)據(jù)源。下面我就分別介紹這兩種連接方式:
假設我要采用的數(shù)據(jù)庫連接是MySQL,所以需要選擇“Database JDBC Connection”方法:

我們可以根據(jù)自己的實際情況來建立數(shù)據(jù)庫連接。值得一提的是,iReport為我們提供我們在實際項目中會遇到的幾乎全部的JDBC驅(qū)動,不管你用的是MySQL,DB2,Orcale還是hsqldb和cloudscape,著實是十分方便。在設定好連接之后,我們就可以在“編輯->報表查詢”中輸入在報表填充所需的查詢語句。如果你設置無誤的話,在你輸入SQL語句之后,iReport會自動為你顯示出你要使用的表的屬性都有哪些:

例如,我的數(shù)據(jù)庫中的表“ky_kyxtbmb”中的屬性在我填入sql語句之后被自動顯示在了下面。對于簡單的報表來說,這樣做著實很方便。但是如果SQL查詢或數(shù)據(jù)庫連接需要變更的話,就需要重新填入SQL或數(shù)據(jù)庫聯(lián)接的信息,并編譯報表設計,這顯然是在損失了靈活性之后所獲得的方便。所以通常我都采用第二種方式。
我們點擊“數(shù)據(jù)=〉連接/數(shù)據(jù)源=〉新建數(shù)據(jù)源”(如下圖所示)。數(shù)據(jù)源類型我們要選擇JavaBean set datasource。選項中Factory Class是用來生成Bean數(shù)據(jù)源的工廠類,它至少包含一個叫做createBeanCollection的方法(當然也可以叫其他的名字)。該工廠類用于為報表提供一個數(shù)組或集合類作為數(shù)據(jù)源,由報表引擎在運行期負責將數(shù)據(jù)讀出,并裝填到報表相應的字段。在利用iReport進行測試的時候,iReport會利用reflection功能在運行期創(chuàng)建一個看不見的工廠類,并調(diào)用其靜態(tài)的數(shù)據(jù)源生成方法來生成數(shù)據(jù)源,最后交給報表引擎負責裝填。

這里應該注意的是,JavaBean中的每一個字段都應該對應報表設計中的一個Field,這一點我們很快就會看到。再有就是我們看到iReport在指定生成數(shù)據(jù)源的靜態(tài)方法的時候并沒有給我們提供設置方法參數(shù)的功能,也就是說如果你的方法需要參數(shù)的話你就不能使用iReport來進行測試。不過這并不會產(chǎn)生很大的影響,我們在程序里測試就是了。

隨后我們要做的是在“選項-〉classpath”中設定classpath,以便iReport能夠找到我們定義的工廠類和相應的JavaBean類。最后,我們在“數(shù)據(jù)-〉報表查詢-〉JavaBean數(shù)據(jù)源”中的類名文本框中填入JavaBean的全限定名,就可以獲得JavaBean的字段名。這時選擇“Add Selected Fields”就可以將這些字段變成報表中的“Field”,于是我們就可以在報表設計中通過“$F{字段名}”來使用它們了。下面是一個使用JavaBean數(shù)組作為數(shù)據(jù)源的例子:
public class SRDataSourceFactory {
/************************************************************************
* 生成實驗室人員知識年齡結(jié)構(gòu)情況的數(shù)據(jù)源
* @return JRDataSource
************************************************************************/
public static JRDataSource createILabMemberInfoDS() throws Exception {
JRBeanCollectionDataSource ds = null;
ArrayList beans = createILabMemberInfoCollection();
ds = new JRBeanCollectionDataSource(beans);
return ds;
}
public static ArrayList createILabMemberInfoCollection() throws Exception {
Connection conn = DBConnection.getConnection();//獲得數(shù)據(jù)庫連接
ArrayList<ILabMemberInfoBean> beans = new ArrayList<ILabMemberInfoBean>();
ILabPaperIndexedBean newBean;
/**
* @todo 下面的代碼主要是將數(shù)據(jù)填入到newBean中,然后將newBean放入到數(shù)組beans里
* 這里就不詳述了。
*/
……
return beans;
}
在實際應用中,很多報表都是用于顯示對數(shù)據(jù)庫信息進行統(tǒng)計查詢的結(jié)果,所以這些報表都不是簡單的二維表,而是帶有復雜的表頭的報表,又或表頭的項目數(shù)量也是動態(tài)的交叉表。對第一種種報表來說,雖然其表頭復雜,但報表的框架卻是靜態(tài)的,僅需要花費些時間在設計統(tǒng)計查詢語句上,采用JavaBean作為數(shù)據(jù)源,運行期由數(shù)據(jù)庫動態(tài)讀取數(shù)據(jù)裝填到報表中就可以了,所以我稱之為“簡單的”,具體實例見表格 2.1‑1和2.1‑2。第二種報表比之前一種復雜了很多,像表格 2.1‑3,這種表多用于顯示統(tǒng)計查詢的結(jié)果,其列的數(shù)量在運行期才能知曉。這需要報表工具專門的支持,而JasperReport為我們提供了支持這種交叉表的“有限”能力。而表格 2.1‑4的情況就更為復雜了,不但列是動態(tài)生成的,而且每一列都是復合表頭。復合表頭也就罷了,然而其表頭的第二層(即指“項目數(shù)”和“經(jīng)費”一層)又來源于不同的屬性,這就超出了目前JasperReport和iReport的能力范疇(至于具體為甚么不能做我在下文還會有交待)。但我們還是有解決的方法:我們或者限定表的列數(shù)(這樣就成了固定表頭的簡單報表了);或者干脆就只能利用JaperReport的API來用程序動態(tài)生成報表設計,然而這顯然是十分復雜的和費事的,也超出了本文的范疇,在這里就不詳述了。下面我們就來看如何實現(xiàn)JasperReport和iReport能力所及的報表的設計。

圖表 2.1‑1復雜表頭的簡單報表(1)

圖表 2.1‑2復雜表頭的簡單報表(2)

圖表 2.1‑3 簡單的交叉表

圖表 2.1‑4 復雜的交叉表
我的例子報表設計在設計器中顯示的效果如下所示:

這個報表結(jié)構(gòu)十分簡單,并沒有用到Group和Subreport及交叉表之類高級技術的,僅僅是為了說明復雜表頭其實并不“復雜”。這個報表的作用是顯示幾個數(shù)據(jù)庫表作統(tǒng)計查詢結(jié)果,只要你在準備工作中正確的設置了參數(shù)(Parameter)和字段(Field),并輸入了正確的查詢語句,就可以獲得想要的結(jié)果。

交叉表(Crosstab—Cross Tabulation)是包含行列合計內(nèi)容的表格,多用于顯示統(tǒng)計結(jié)果,在工作中十分常用。JasperReport是在JasperReport1.1中開始支持這項功能的,然而其功能目前仍顯稚嫩,還不能完成更為復雜的一些的操作,如圖表 2.1‑4。不過聊勝于無,BIRT要到下一個版本才支持交叉表呢。下面就讓我們具體看一下如何生成像圖表 2.1‑3這樣行列都帶統(tǒng)計,且右下角的方格顯示總計數(shù)值的報表設計吧。
2.2.1利用iReport向?qū)山徊姹砑軜?gòu)
首先我們生成一個新的報表,在其summery帶中放置一個交叉表,這時iReport就會出現(xiàn)交叉表生成向?qū)韼椭覀冊O置交叉表的結(jié)構(gòu)。跟據(jù)圖2.1‑3的結(jié)構(gòu),我們看到表的行是飛機要飛往的城市名稱,而列示飛機行班的名字。每一列的total指在每一個城市中某一架飛機的航班數(shù),而每行的total表示某一城市中所有航班的數(shù)量,表的右下角為所有城市的總航班數(shù)。

可以看到,iReport的向?qū)J給我們提供了兩個Row Group的能力,舉例來說,我們擴展圖2.1‑3,使其顯示每個州的各個城市的飛機航班情況,就需要將Row Group1 設置為州(?。┧鶎膶傩?,而將Row Group 2置為城市所對應的屬性。同樣,我們也可以設置2個Column Group。但需要注意的是,我們發(fā)現(xiàn)這樣的配置是不可能實現(xiàn)例如表2.1‑4這樣的結(jié)構(gòu)的。因為表2.1‑4中對應Column Group2的部分并不是取自一個屬性,這樣JasperReport就無法將其組織在一個格中。所以我們只能實現(xiàn)比2.1‑3的行/列多一個Group(即行列各多一維)但屬性為單一屬性的報表,而無法生成類似于2.1‑4這樣的復雜結(jié)構(gòu)。最后要說的是,如果有需要的話我們可以交叉表向?qū)ЫY(jié)束之后自行在交叉表的屬性選項卡中加入新的行/列Group,而不僅僅局限于iReport向?qū)е刑峁┑膬蓚€。

在表的內(nèi)容(Detail Field)部分我們也可以有如上圖所示的3種選擇,這一選項用來指示JasperReport如何進行運算。最后我們可以定義對total的配置,即是否加入行總計,是否加入列總計和是否顯示表格線,本例中我們都是需要的。

至此,表格的基本骨架生成完畢,我們可以在設計器中看到如下內(nèi)容:

然后我們在查詢窗口寫如下查詢語句即可:

可以看到,我們并沒有使用任何統(tǒng)計查詢的語句,而JasperReport就能為我們自動進行統(tǒng)計運算,并將結(jié)果填入指定位置,它是怎么做到的呢?細心的人可能已經(jīng)看出一點問題來了,怎么報表設計中的文本框里寫的都是變量V而不是我們常用的字段F呢?這就是原因所在,JasperReport通過一個“Measure”,即我們在Detail Field部分定義的屬性,根據(jù)其運算類型(包括Average, Count, First, Highest, Lowest, Nothing, StandardDeviation, Sum和Variance.)來對行/列的數(shù)量進行運算,并將結(jié)果存放在內(nèi)部變量中(這些變量我們在iReport的變量查看器中是看不見的),并利用這些變量來顯示統(tǒng)計結(jié)果。然而我們可以清楚地看到,這樣的功能設定還是有很多問題的,例如如果我們的統(tǒng)計查詢不能夠僅由一條查詢語句就能表示怎么辦?就算用一條查詢就能表示的統(tǒng)計,遇到像表2.1‑4這樣的結(jié)構(gòu)我們照樣無計可施。所以在實在需要的情況下我們就只能借助于JasperReportAPI動態(tài)生成報表的JRXML文件來獲得想要的靈活性,這是JasperReport的萬靈丹,只不過需要付出更多的精力罷了。在JasperReport和IReport變得更強大之前,我們也就只能利用現(xiàn)有的工具作一些有限的工作了。
對于交叉表來說,其難點在于報表的內(nèi)容屬于統(tǒng)計查詢,不是一個查詢語句就能獲得所有想要的數(shù)據(jù),而且表的列是不能在設計期就知曉的,進而不能像準備工作中所提到的那樣設置一條查詢就完事大吉。但是也可以有一個比較省事的方法是:將報表所需要的所有數(shù)據(jù)在數(shù)據(jù)庫提取出來之后放到一個臨時的表(或其他JasperReport數(shù)據(jù)源所支持的數(shù)據(jù)結(jié)構(gòu)中,例如JavaBean)然后再將數(shù)據(jù)逐一填到報表中。對于表格 2.1‑2這樣的報表,這種方法既實用又很簡單,但是這種做法不適合列也是動態(tài)的交叉表—例如表格 2.1‑3。所以對于最后一種情況我們就沒什么好說的,必須使用JasperReport的Crosstab的相關功能了。以上就是利用JasperReport+iReport進行報表設計的全部內(nèi)容,限于篇幅,我只能盡量挑選一些網(wǎng)上人們問題的最多的普遍問題加以解釋,說明了什么是JasperReport能做的,什么是它不能做或做起來很麻煩的,也大略講解了究竟怎么做。希望能給各位在做報表時提供一點幫助。
作者簡介:本文作者薛笛,是黑龍江大學研究生。他目前在黑龍江大學信息技術研究所工作,從事傳感器網(wǎng)絡和移動數(shù)據(jù)庫的研究,對Java技術特別感興趣。可以通過 jxuedi@gmail.com 與他聯(lián)系。