一.JXLS簡介
在很多涉及到某種報表功能的Java程序中都需要生成Excel表格。目前通過Java來操作.xls文件最完整的類庫是Apache POI類庫,但是當需要創建多種自定義的復雜Excel報表的時候就會出現問題,這些Excel報表一般都帶有多種格式和可擴展功能,在這種情況下,你就不得不寫一大堆Java代碼來創建報表的規則集(workbook),規則集一般包含所有要求的格式,公式,其他特定的設置和正確的Java對象集的數據出口。這些代碼一般都是難以調試,任務也常常變得容易出錯並且耗時。
另外一個問題是有很多Excel組件都沒有提供的API。幸運的是POI API讀取Excel文件,可以保持它原有的格式,然后根據需要進行修改。很明顯,用一些Excel編輯工具來創建所有格式正確的報告模板然后指定真實的數據應該放置的地方,會容易很多。JXLS是實現這種方法並且只用幾行代碼就能創建極其復雜的Excel報表。你只需要用特定的標記來創建一個帶有所有要求的格式,公式,宏等規則的.xls模板文件來指定數據放置的位置然后再寫幾行代碼來調用JXLS引擎來傳遞.xls模板和導出的數據作為參數。
除了生成Excel報表功能,JXLS還提供了jxls-reader模塊,jxls-reader模塊會很有用,如果你需要解析一個預定義格式的Excel文件並在其中插入數據的話。jxls-reader允許你用一個簡單的XML文件描述解析規則,讀取Excel文件和你的各種JAVA對象(population of yourJava objects)的所有其他工作都會自動完成。
二.JXLS安裝
為了使用JXLS引擎,你必須把jxls-core.jar添加到項目的classpath,如果計划使用JXLS來讀取.xls文件,那么你必須還要把jxls-reader.jar加入到項目的classpath中。
如果你用Maven來構建你的應用程序,你可以在你的pom.xml文件中配置指定要求的JXLS模塊的依賴,讓它們可以從Maven倉庫下載。
下面的Apache類庫也要求添加到項目的classpath中
●POI 3.6 or higher
●Commons BeanUtils
●Commons Collections
●Commons JEXL
●Commons Logging
●Commons Digester
注意:當前JXLS版本可能無法正常地與較早的POI庫版本工作,因此,如果你必須要使用較早版本的POI(prior3.2)使用較老版本的JXLS就行了
三.JXLS參考
1.簡介
這部分描述在.xls模板文件的中的對象屬性訪問語法,如果想讓JXLS引擎進行正確的處理.xls模板文件就必須使用規定的語法。
接下來的部分假設我們有兩個相互依賴的JAVA beans,類型分別為Department和Employee,在代碼中像這樣被傳遞到XLSTransformer中:
Departmentdepartment;
...//initialization
Map beans =new HashMap();
beans.put("department",department);
XLSTransformertransformer = new XLSTransformer(); transformer.transformXLS(xlsTemplateFileName,beans, outputFileName);
2. 屬性訪問
2.1.基本屬性訪問
使用下面的語句來訪問Excel單元格中簡單的bean屬性:
${department.name}
在上面這個語句中,JXLS引擎會通過關鍵字department在當前bean映射下搜索這個bean,然后會嘗試獲取這個bean的name屬性的值並把它放到相應的Excel單元格中。
同理,我們可以訪問更加復雜的屬性,例如,要輸出這個department中的屬性chief中的name屬性的值,我們可以用:
${department.chief.name}
訪問任何深度的對象屬性都是可以的。例如
${bean.bean1.bean2.bean3.bean4.bean5.bean6.bean7.bean8.bean9.beanX.property1}
2.2多個屬性在一個單元格中
在一個單元格,我們可以連接幾個屬性。例如:
Employee:${employee.name} - ${employee.age} years
這樣,我們得到的輸出是:
Employee: John -35 years
其中${employee.name}的值是John,同理${employee.age}的值是35.
3.使用標簽
JXLS允許在模板中使用預定義的XML標簽來控制XLS轉換行為。
3.1 jx:forEach標簽
<jx:forEach>標簽的典型用法如下:
<jx:forEach items="${departments}"var="department">
${department.name}| ${department.chief}
</jx:forEach>
jx標簽可以相互嵌套使用
如果你把jx:forEach標簽的開始標簽和結束標簽放在同一行的話,JXLS會在同一行上重復在jx:forEach標簽的開始標簽和結束標簽之間的Excel單元格。
目前,如果你想要用jx:forEach標簽重復Excel的行,那么你必須把jx:forEach標簽的開始標簽和結束標簽放在不同的行,把要重復的行包含在中間,jx:forEach標簽所在行的所有單元格都會被忽略。
以下是一個實例的截圖:
圖.1模板文件
圖.2生成的Excel報表
3.1.2 forEach標簽的數據分組
jx:forEach標簽可以通過一個底層bean的屬性對數據集合的分組,這可以通過jx:forEach標簽的groupBy and groupOrder屬性完成,groupBy用於指定一個屬性進行分組,groupOrder用於指定各個分組排列順序。例如:
<jx:forEachitems="${employees}" groupBy="age">
Age: ${group.item.age}
<jx:forEachitems="${group.items}" var="employee">
${employee.name} |${employee.payment} | ${employee.bonus}
</jx:forEach>
</jx:forEach>
在這個例子中,我們把employees按age屬性分組,當遇到groupBy屬性的時候,JXLS內部執行分組並且放置名為group的新的bean到上下文中,這個新的bean是很簡單-它包含兩個屬性:item屬性和items屬性,item屬性是分組中當前處理的bean;items屬性
代表這個分組中所有bean的集合。
正如你看到的,在這個例子中我們首先使用下列語句顯示一些關於當前分組age屬性的信息
Age:${group.item.age}
之后,我們使用內部<jx:forEach>標簽來實現迭代並顯示所有分組中的記錄
<jx:forEachitems="${group.items}" var="employee">
${employee.name}| ${employee.payment} | ${employee.bonus}
</jx:forEach>
默認情況下,如果沒有groupOrder屬性組的順序將被按“原先”的順序保留,以便這些分組的排列順序跟原先的集合中一樣,如果你需要按升序或是降序排列排列這些分組,那么你可以將groupOrder屬性相應地設置為ASC或DESC
在使用groupBy屬性的情況下,jx:forEach標簽的var屬性將被忽略
3.1.3forEach標簽的篩選功能
你可以用jx:forEach標簽的‘select’屬性來選擇把哪些記錄包含在循環中,例如,如果我們想只包含工資高於 2000元的員工,我們可以使用下面的語句:
<jx:forEach items="${employees}"var="employee" select="${employee.payment > 2000}">
${employee.name}| ${employee.payment} | ${employee.bonus}
</jx:forEach>
3.14 forEach標簽的varStatus 屬性
jx:forEach標簽支持varStatus屬性,varStatus屬性用來定義一個循環狀態的名字,在每一次迭代中,循環狀態對象會被傳遞到bean上下文。循環狀態對象是LoopStatus類的一個實例,LoopStatus類有一個單一(靜態)的'index'屬性用來確定當前記錄在集合中的索引值(索引值從0開始)。
<jx:forEachitems="${employees}" var="employee"varStatus="status">
${status.index}|${employee.name}|${employee.payment}|${employee.bonus}
</jx:forEach>
3.2 jx:if標簽
典型的<jx:if>標簽的用法如下:
<jx:iftest="${department.chief.payment > 2000.0}">
Chief Name: ${department.chief.name}
</jx:if>
jx:if標簽可以基於某些條件來排除某些行或是某些列,如果你把jx:if標簽的開始標簽和結束標簽放在同一行的話,JXLS會根據test的條件來處理或刪除包含在標簽提內的列;如果你把jx:if標簽的開始標簽和結束標簽放在不同行的話,JXLS會根據test的條件來處理或刪除包含在標簽提內的行。
3.3 jx:outline標簽
<jx:outline>標簽可以將特定的行組成一組。例如:
圖.3 模板文件
圖.4 產生的Excel文件(折疊)
圖.5 產生的Excel文件(展開)
<jx:outline>標記有一個可選的布爾類型的屬性“detail”聲明結果初始化的狀態-它們應該展開顯示還是折疊顯示,默認值是false即分組的行會被折疊顯示(隱藏)
3.4 jx:out標簽
<jx:out>標簽的用法如下:
<jx:outexpr="expression" />
這個標簽可以在任何地方使用,表達式將正常使用,只是將JXLS表達式作為屬性值。
例如:
圖.6 模板文件
圖.7 生成的Excel文件
4. 執行SQL查詢
在許多企業級應用中,Excel報表起着非常重要的作用,現在JXLS可以直接吧SQL查詢語句直接寫入.xls模板文件中,這樣在模板進行轉換的時候,SQL語句會被執行,所有的查詢結果都會正確地填入Excel報表中。
要執行SQL查詢,並在Excel文件中顯示查詢結果,你必須在模板進行轉換之前把一個特殊的bean寫入bean上下文,這個特殊的bean要實現ReportManager接口。目前這個接口只有一個方法:
public Listexec(String sql) throws SQLException
這個方法的參數是一個SQL查詢語句,執行結果,返回一個list(list的泛型是bean)
JXLS對這個接口提供了一個默認的實現,叫做ReportManagerImpl,ReportManagerImpl使用owSetDynaClass來把ResultSet對象封裝到對象集合中,下面是這個類的用法:
Connection conn =...// get database connection in some way
Map beans = newHashMap();
ReportManager rm =new ReportManagerImpl( conn, beans );
beans.put("rm",rm);
InputStream is =new BufferedInputStream(new FileInputStream("reportTemplate.xls"));
XLSTransformertransformer = new XLSTransformer();
HSSFWorkbookresultWorkbook = transformer.transformXLS(is, beans);
ReportManagerImpl構造函數將數據庫連接對象和bean的map作為參數傳遞給XLSTransformer,然后把ReportManager對象放到bean上下文中使用“rm”作為key。這意味着,我們可以執行任何SQL查詢語句通過把它作為參數傳遞給rm.exec()方法,例如:
${rm.exec("SELECTname, age FROM employee")}
通常情況下,這個語句和jx:forEach標簽結合起來使用來遍歷bean的集合(ResultSet)並把它顯示在Excel文件中,例如:
<jx:forEachitems="${rm.exec('SELECT e.name, e.age, e.payment FROM employee e')}"var="employee">
${employee.name}| ${employee.age} | ${employee.payment}
</jx:forEach>
可以使用jx:forEach
標簽的
groupBy
屬性來實現按某些列的值進行分組查詢。
以下是一個實例:
. . .//
主要代碼
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn=DriverManager.getConnection(url,"gaps32", "gaps32");
Map beans = new HashMap();
ReportManager rm = new ReportManagerImpl( conn, beans );
beans.put("rm", rm);
XLSTransformer transformer = new XLSTransformer();
transformer.transformXLS(report, beans,reportDest );
conn.close();
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (SQLException e2){
e2.printStackTrace();
}//. . .
圖.8 模板文件
圖.9 生成的Excel
4.2 依賴查詢
如果使用jdbc的數據庫驅動話,你可以給ReportManagerImpl傳遞任何SQL語句,你可以把在一個查詢中使用另一個查詢的結果,你可以把一個查詢的結果放到bean上下文中,當在依賴查詢需要使用這個結果的時候就可以使用了。依賴查詢(子查詢)典型的用法可以用在兩個jx:forEach標簽,當其中一個標簽嵌套在令一個標簽里面的時候,下面是一個簡單的例子:
<jx:forEach items="${rm.exec('SELECT d.name, d.id FROM department d')}"var="dep">
Department:${dep.name}
Name |Payment | Bonus | Total
<jx:forEach items="${rm.exec('SELECT name,age, payment, bonus, birthDate FROM employee e where e.depid = ' +dep.id)}" var="employee">
${employee.empname}|${employee.payment}|${employee.bonus}|$[B23*(1+C23)]
</jx:forEach>
</jx:forEach>
在這里我們把第一次查詢到的department信息放到上下文中以dep作為key,然后在內部的jx:forEach標簽中使用
<jx:forEachitems="${rm.exec('SELECT name, age, payment, bonus, birthDate FROMemployee e where e.depid = ' + dep.id)}" var="employee">
JXLS會替換掉當前處理的部門的id值以便於能查詢到當前部門的所有員工
4.3 查詢語句包含參數
前面的例子已經顯示了如何使用內部SQL查詢參數,此外,我們還可以使用外部參數,如果我們把它放在bean上下文中。
Map beans = new HashMap();
ReportManager reportManager = new ReportManagerImpl(conn, beans );
beans.put("rm", reportManager);
beans.put("minDate", "1979-01-01");
XLSTransformer transformer = new XLSTransformer();
transformer.transformXLS(templateFileName, beans,destFileName);
上面我們把日期“1979-01-01”放到bean的上下文中,以minDate作為key,下面我們使用它來構建一條查詢語句:
<jx:forEachitems="${rm.exec("SELECT d.name depname, e.name empname, age,payment, bonus, birthDate FROM employee e, department d WHERE d.id = e.depidAND birthDate > '1975-01-01' AND birthDate < '" + minDate + "'order by age desc")}" var="employee">
你要可以從上面的語句中了解到如何在SQL查詢語句中使用單引號。
4.4 JDBC結果集
雖然JXLS沒有為JDBC查詢結果集設計的數據出口,但是查詢結果集可以使用Commons BeanUtils的動態類很容易進行輸出,XLSTransformer有兩種方法可以導出結果集中的數據,第一種使用org.apache.commons.beanutils.RowSetDynaClass,第二種是基於類net.sf.jxls.report.ResultSetCollection。
4.4.1基於RowSet的輸出
當你構建一個org.apache.commons.beanutils.RowSetDynaClass類的實例的時候,底層數據被復制到內存中的動態bean集合中,這個集合就代表結果,所以你可以馬上關閉結果集,通常當你處理實際數據之前,結果集就已經關閉了,這種方法的缺點就是你必須消耗一定的性能和內存來復制這些結果數據。
使用這種方法首先你必須構建一個新的RowSetDynaClass的實例,並把結果集傳遞給它。
ResultSetresultSet = ...
RowSetDynaClassrowSet = new RowSetDynaClass(resultSet, false);
第二個構造函數的參數表示屬性名稱在動態bean的結果集中不應該小寫。在你初始化RowSetDynaClass的實例之后,你可以調用它的getRows()方法來獲得動態bean的結果集,並用通常的方法輸出。
Map beans = newHashMap();
beans.put("employee", rowSet.getRows() );
XLSTransformertransformer = new XLSTransformer();
transformer.transformXLS(templateFileName, beans, destFileName);
下面是一個實例:
…//
Statement stmt = conn.createStatement();
String query = "SELECT * FROM SAI";
rs = stmt.executeQuery(query);
Map beans = newHashMap();
RowSetDynaClass rsc = new RowSetDynaClass(rs,false);
beans.put("employee", rsc.getRows());
XLSTransformer transformer = new XLSTransformer();
transformer.transformXLS(report, beans,reportDest );
conn.close();//…
圖.10模板文件
圖.11生成Excel文件
4.4.2 結果集輸出
如果你不想在內存中加載所有的結果集數據,並同意在處理數據的時候一直保持數據庫連接不斷開的話,你可以使用net.sf.jxls.report.ResultSetCollection類,這個類以ResultSet(結果集)為參數並實現了Collection接口來操作底層數據,反過來ResultSetCollection使用org.apache.commons.beanutils.ResultSetDynaClass來返回檢索到的數據作為動態對象。
下面是net.sf.jxls.report.ResultSetCollection
類的使用:
ResultSetCollectionrsc = new ResultSetCollection(resultSet, false);
beans.put("employee", rsc );
第二個構造函數的參數,表明屬性名稱在處理之前不應該小寫。
下面是一個實例:
…//
Class.forName("oracle.jdbc.driver.OracleDriver");
conn=DriverManager.getConnection(url,"gaps32", "gaps32");
Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
String query = "SELECT NAME, AGE, PAYMENT, BONUS, IDFROM SAI";
rs = stmt.executeQuery(query);
Map beans = newHashMap();
ResultSetCollection rsc = new ResultSetCollection(rs,false);
beans.put( "employee", rsc );
XLSTransformer transformer = new XLSTransformer();
transformer.transformXLS(report, beans,reportDest );
//…
注:不能使用Statement stmt=conn.createStatement();不然會出現異常(對只轉發結果集的無效操作: last "的異常)應該使用Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
分析:異常出現於移動結果集的指針時,原因是在生成statement對象的時候提供的參數不同無參數的那個方法使用的是默認參數,statement執行后得到的結果集類型為 ResultSet.TYPE_FORWARD_ONLY.這種類型的結果集只能通過rs.next();方法逐條讀取,使用其他方法就會報異常. 如果想執行一些復雜的移動結果集指針的操作就要使用其他參數了
圖.12模板文件
圖.13生成的Excel文件
4.5 在報表中插入圖表
List staff = newArrayList();
staff.add(new Employee("Derek", 35, 3000, 0.30));
staff.add(new Employee("Elsa", 28, 1500, 0.15));
staff.add(new Employee("Oleg", 32, 2300, 0.25));
staff.add(new Employee("Neil", 34, 2500, 0.00));
staff.add(new Employee("Maria", 34, 1700, 0.15));
staff.add(new Employee("John", 35, 2800, 0.20));
staff.add(new Employee("Leonid", 29, 1700, 0.20));
Map beans = newHashMap();
beans.put("employee", staff);
XLSTransformer transformer =new XLSTransformer();
transformer.markAsFixedSizeCollection("employee");
transformer.transformXLS(templateFileName, beans,destFileName);
源碼文件和普通的文件一樣。
圖.14模板文件
在Excel模板中插入圖表,在工具欄選插入-----圖表,然后選擇圖表的類型,有柱狀圖,餅狀圖等等,插入后,選中圖表右擊選擇“選擇數據”然后對圖表中的數據進行設置,(橫軸和水平軸)插入圖表有一個缺陷,圖表中的數據條數要固定,而且要預先設置好,不能在運行的時候確定,然后如果數據量太大的話,顯示會很擁擠。
四.注釋
Item-----記錄(上文中把英文資料中的Item翻譯為記錄)
作者:萬瓊瑤