序言
一直以來,報表都是很多項目中一個重要的、不可獲取的組成部分。然而其復雜性和專業性又使得程序員不能夠也沒時間自己設計屬于目前手頭正在構建的系統的報表模塊;即便設計來了又可能由于通用性等原因不能夠應用到下一個系統當中,這就導致了報表類庫/組件在市場上的火爆。典型的例子就是水晶報表,幾乎無處不在。還有一些專門處理中國式報表的組件也陸續出現在軟件市場上。然而遺憾的是,他們中的絕大多數都是要收費的--這也無可厚非,畢竟人都是要生存的。所以大多數預算不夠充裕的程序員都將目光轉向了開源軟件,而JasperReport就將是第一個進入他們視線中的佼佼者。
然而代碼開源不代表我們就可以大大方方的拿來就用了,人家的文檔也是要收費的,所以市面上有關于JasperReport的文檔雖然也不少,但大多數都集中在簡單應用和介紹基本操作的基礎之上。對于復雜的報表設計并不能提供良好的幫助。本文將火力集中在相對復雜的報表設計(不包括圖片和超連接等對辦公系統或過程沒有太大用途的頁面元素),交叉表的生成等高級報表設計方案。對于那些基本的操作則留給讀者自行體會,相信可以為各位解決一些實際問題。
JasperReport是JasperSoft公司的一款開源的報表解決方案。通過JasperReport,用戶就可以方便的定制、設計、生成項目所需要的各種報表。和JasperReport一樣,iReport也是Sourceforg上的開源項目。它的出現,主要是為了降低JasperReport的使用難度,為用戶提供可視化的報表設計工具,目前iReport的版本號為1.2.7。
每個版本的iReport都會與最新的JasperReport相匹配的功能,并可以手動設置與用戶所使用的JasperReport兼容性,以便懷舊的用戶可以無障礙地使用最新的iReport:
限于篇幅,我不可能把iReport操作的每一個細節都記錄在本文中,而只能對涉及到的部分作出簡要介紹,剩下的諸如報表中的各個報表元素是什么,屬性都有什么;什么是iReport的字段(Filed),參數(parameter)和變量(variable)等等這些基本概念,如果想要深究細節的話有兩種選擇:1是尋找其官方文檔(要收費),網上流傳的文檔版本較低,但是也可以作為參考,如果想要我翻譯的JasperReport用戶文檔,可以給我發郵件。2是自己動手實踐一下吧。
做報表的目的就是顯示數據,無論是簡單的查詢結果還是某些統計信息。所以我們第一步要做的就是設定iReport中的數據源或數據庫連接,以便于從數據庫中動態獲取數據。在菜單->Connection and Datasource里我們可以設定所要采用的數據提供方式。iReport為我們提供了豐富的選項:
通常情況下,我們一般有兩種選擇,一是選擇數據庫連結的數據源,通過這種方式,我們就可以直接在報表設計中寫入SQL查詢語句,讓報表在運行期自動獲取所需的數據來裝填報表而不需要做額外的工作。但這樣一來,就必然會損失一些程序的靈活性,比如查詢語句或數據庫連接需要修改的時候我們就不得不重新填入相應的內容并編譯報表。所以我在工作中通常采用第二種方式,即用JavaBean的集合(Collection或Array)來充當數據源。下面我就分別介紹這兩種連接方式:
假設我要采用的數據庫連接是MySQL,所以需要選擇“Database JDBC Connection”方法:
我們可以根據自己的實際情況來建立數據庫連接。值得一提的是,iReport為我們提供我們在實際項目中會遇到的幾乎全部的JDBC驅動,不管你用的是MySQL,DB2,Orcale還是hsqldb和cloudscape,著實是十分方便。在設定好連接之后,我們就可以在“編輯->報表查詢”中輸入在報表填充所需的查詢語句。如果你設置無誤的話,在你輸入SQL語句之后,iReport會自動為你顯示出你要使用的表的屬性都有哪些:
例如,我的數據庫中的表“ky_kyxtbmb”中的屬性在我填入sql語句之后被自動顯示在了下面。對于簡單的報表來說,這樣做著實很方便。但是如果SQL查詢或數據庫連接需要變更的話,就需要重新填入SQL或數據庫聯接的信息,并編譯報表設計,這顯然是在損失了靈活性之后所獲得的方便。所以通常我都采用第二種方式。
我們點擊“數據=〉連接/數據源=〉新建數據源”(如下圖所示)。數據源類型我們要選擇JavaBean set datasource。選項中Factory Class是用來生成Bean數據源的工廠類,它至少包含一個叫做createBeanCollection的方法(當然也可以叫其他的名字)。該工廠類用于為報表提供一個數組或集合類作為數據源,由報表引擎在運行期負責將數據讀出,并裝填到報表相應的字段。在利用iReport進行測試的時候,iReport會利用reflection功能在運行期創建一個看不見的工廠類,并調用其靜態的數據源生成方法來生成數據源,最后交給報表引擎負責裝填。
這里應該注意的是,JavaBean中的每一個字段都應該對應報表設計中的一個Field,這一點我們很快就會看到。再有就是我們看到iReport在指定生成數據源的靜態方法的時候并沒有給我們提供設置方法參數的功能,也就是說如果你的方法需要參數的話你就不能使用iReport來進行測試。不過這并不會產生很大的影響,我們在程序里測試就是了。
隨后我們要做的是在“選項-〉classpath”中設定classpath,以便iReport能夠找到我們定義的工廠類和相應的JavaBean類。最后,我們在“數據-〉報表查詢-〉JavaBean數據源”中的類名文本框中填入JavaBean的全限定名,就可以獲得JavaBean的字段名。這時選擇“Add Selected Fields”就可以將這些字段變成報表中的“Field”,于是我們就可以在報表設計中通過“$F{字段名}”來使用它們了。下面是一個使用JavaBean數組作為數據源的例子:
public class SRDataSourceFactory {
/************************************************************************
* 生成實驗室人員知識年齡結構情況的數據源
* @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();//獲得數據庫連接
ArrayList<ILabMemberInfoBean> beans = new ArrayList<ILabMemberInfoBean>();
ILabPaperIndexedBean newBean;
/**
* @todo 下面的代碼主要是將數據填入到newBean中,然后將newBean放入到數組beans里
* 這里就不詳述了。
*/
……
return beans;
}
在實際應用中,很多報表都是用于顯示對數據庫信息進行統計查詢的結果,所以這些報表都不是簡單的二維表,而是帶有復雜的表頭的報表,又或表頭的項目數量也是動態的交叉表。對第一種種報表來說,雖然其表頭復雜,但報表的框架卻是靜態的,僅需要花費些時間在設計統計查詢語句上,采用JavaBean作為數據源,運行期由數據庫動態讀取數據裝填到報表中就可以了,所以我稱之為“簡單的”,具體實例見表格 2.1‑1和2.1‑2。第二種報表比之前一種復雜了很多,像表格 2.1‑3,這種表多用于顯示統計查詢的結果,其列的數量在運行期才能知曉。這需要報表工具專門的支持,而JasperReport為我們提供了支持這種交叉表的“有限”能力。而表格 2.1‑4的情況就更為復雜了,不但列是動態生成的,而且每一列都是復合表頭。復合表頭也就罷了,然而其表頭的第二層(即指“項目數”和“經費”一層)又來源于不同的屬性,這就超出了目前JasperReport和iReport的能力范疇(至于具體為甚么不能做我在下文還會有交待)。但我們還是有解決的方法:我們或者限定表的列數(這樣就成了固定表頭的簡單報表了);或者干脆就只能利用JaperReport的API來用程序動態生成報表設計,然而這顯然是十分復雜的和費事的,也超出了本文的范疇,在這里就不詳述了。下面我們就來看如何實現JasperReport和iReport能力所及的報表的設計。
圖表 2.1‑1復雜表頭的簡單報表(1)
圖表 2.1‑2復雜表頭的簡單報表(2)
圖表 2.1‑3 簡單的交叉表
圖表 2.1‑4 復雜的交叉表
我的例子報表設計在設計器中顯示的效果如下所示:
這個報表結構十分簡單,并沒有用到Group和Subreport及交叉表之類高級技術的,僅僅是為了說明復雜表頭其實并不“復雜”。這個報表的作用是顯示幾個數據庫表作統計查詢結果,只要你在準備工作中正確的設置了參數(Parameter)和字段(Field),并輸入了正確的查詢語句,就可以獲得想要的結果。
交叉表(Crosstab—Cross Tabulation)是包含行列合計內容的表格,多用于顯示統計結果,在工作中十分常用。JasperReport是在JasperReport1.1中開始支持這項功能的,然而其功能目前仍顯稚嫩,還不能完成更為復雜的一些的操作,如圖表 2.1‑4。不過聊勝于無,BIRT要到下一個版本才支持交叉表呢。下面就讓我們具體看一下如何生成像圖表 2.1‑3這樣行列都帶統計,且右下角的方格顯示總計數值的報表設計吧。
首先我們生成一個新的報表,在其summery帶中放置一個交叉表,這時iReport就會出現交叉表生成向導來幫助我們設置交叉表的結構。跟據圖2.1‑3的結構,我們看到表的行是飛機要飛往的城市名稱,而列示飛機行班的名字。每一列的total指在每一個城市中某一架飛機的航班數,而每行的total表示某一城市中所有航班的數量,表的右下角為所有城市的總航班數。
可以看到,iReport的向導默認給我們提供了兩個Row Group的能力,舉例來說,我們擴展圖2.1‑3,使其顯示每個州的各個城市的飛機航班情況,就需要將Row Group1 設置為州(省)所對應的屬性,而將Row Group 2置為城市所對應的屬性。同樣,我們也可以設置2個Column Group。但需要注意的是,我們發現這樣的配置是不可能實現例如表2.1‑4這樣的結構的。因為表2.1‑4中對應Column Group2的部分并不是取自一個屬性,這樣JasperReport就無法將其組織在一個格中。所以我們只能實現比2.1‑3的行/列多一個Group(即行列各多一維)但屬性為單一屬性的報表,而無法生成類似于2.1‑4這樣的復雜結構。最后要說的是,如果有需要的話我們可以交叉表向導結束之后自行在交叉表的屬性選項卡中加入新的行/列Group,而不僅僅局限于iReport向導中提供的兩個。
在表的內容(Detail Field)部分我們也可以有如上圖所示的3種選擇,這一選項用來指示JasperReport如何進行運算。最后我們可以定義對total的配置,即是否加入行總計,是否加入列總計和是否顯示表格線,本例中我們都是需要的。
至此,表格的基本骨架生成完畢,我們可以在設計器中看到如下內容:
然后我們在查詢窗口寫如下查詢語句即可:
可以看到,我們并沒有使用任何統計查詢的語句,而JasperReport就能為我們自動進行統計運算,并將結果填入指定位置,它是怎么做到的呢?細心的人可能已經看出一點問題來了,怎么報表設計中的文本框里寫的都是變量V而不是我們常用的字段F呢?這就是原因所在,JasperReport通過一個“Measure”,即我們在Detail Field部分定義的屬性,根據其運算類型(包括Average, Count, First, Highest, Lowest, Nothing, StandardDeviation, Sum和Variance.)來對行/列的數量進行運算,并將結果存放在內部變量中(這些變量我們在iReport的變量查看器中是看不見的),并利用這些變量來顯示統計結果。然而我們可以清楚地看到,這樣的功能設定還是有很多問題的,例如如果我們的統計查詢不能夠僅由一條查詢語句就能表示怎么辦?就算用一條查詢就能表示的統計,遇到像表2.1‑4這樣的結構我們照樣無計可施。所以在實在需要的情況下我們就只能借助于JasperReportAPI動態生成報表的JRXML文件來獲得想要的靈活性,這是JasperReport的萬靈丹,只不過需要付出更多的精力罷了。在JasperReport和IReport變得更強大之前,我們也就只能利用現有的工具作一些有限的工作了。
對于交叉表來說,其難點在于報表的內容屬于統計查詢,不是一個查詢語句就能獲得所有想要的數據,而且表的列是不能在設計期就知曉的,進而不能像準備工作中所提到的那樣設置一條查詢就完事大吉。但是也可以有一個比較省事的方法是:將報表所需要的所有數據在數據庫提取出來之后放到一個臨時的表(或其他JasperReport數據源所支持的數據結構中,例如JavaBean)然后再將數據逐一填到報表中。對于表格 2.1‑2這樣的報表,這種方法既實用又很簡單,但是這種做法不適合列也是動態的交叉表—例如表格 2.1‑3。所以對于最后一種情況我們就沒什么好說的,必須使用JasperReport的Crosstab的相關功能了。以上就是利用JasperReport+iReport進行報表設計的全部內容,限于篇幅,我只能盡量挑選一些網上人們問題的最多的普遍問題加以解釋,說明了什么是JasperReport能做的,什么是它不能做或做起來很麻煩的,也大略講解了究竟怎么做。希望能給各位在做報表時提供一點幫助。
作者簡介:本文作者薛笛,是黑龍江大學研究生。他目前在黑龍江大學信息技術研究所工作,從事傳感器網絡和移動數據庫的研究,對Java技術特別感興趣。可以通過 jxuedi@gmail.com 與他聯系。