一.JXLS簡(jiǎn)介
在很多涉及到某種報(bào)表功能的Java程序中都需要生成Excel表格肢执。目前通過Java來操作.xls文件最完整的類庫是Apache POI類庫抢韭,但是當(dāng)需要?jiǎng)?chuàng)建多種自定義的復(fù)雜Excel報(bào)表的時(shí)候就會(huì)出現(xiàn)問題她渴,這些Excel報(bào)表一般都帶有多種格式和可擴(kuò)展功能,在這種情況下,你就不得不寫一大堆Java代碼來創(chuàng)建報(bào)表的規(guī)則集(workbook),規(guī)則集一般包含所有要求的格式,公式,其他特定的設(shè)置和正確的Java對(duì)象集的數(shù)據(jù)出口夕吻。這些代碼一般都是難以調(diào)試,任務(wù)也常常變得容易出錯(cuò)并且耗時(shí)繁仁。
另外一個(gè)問題是有很多Excel組件都沒有提供的API涉馅。幸運(yùn)的是POI API讀取Excel文件,可以保持它原有的格式黄虱,然后根據(jù)需要進(jìn)行修改稚矿。很明顯,用一些Excel編輯工具來創(chuàng)建所有格式正確的報(bào)告模板然后指定真實(shí)的數(shù)據(jù)應(yīng)該放置的地方捻浦,會(huì)容易很多盐捷。JXLS是實(shí)現(xiàn)這種方法并且只用幾行代碼就能創(chuàng)建極其復(fù)雜的Excel報(bào)表。你只需要用特定的標(biāo)記來創(chuàng)建一個(gè)帶有所有要求的格式,公式,宏等規(guī)則的.xls模板文件來指定數(shù)據(jù)放置的位置然后再寫幾行代碼來調(diào)用JXLS引擎來傳遞.xls模板和導(dǎo)出的數(shù)據(jù)作為參數(shù)默勾。
除了生成Excel報(bào)表功能,JXLS還提供了jxls-reader模塊聚谁,jxls-reader模塊會(huì)很有用母剥,如果你需要解析一個(gè)預(yù)定義格式的Excel文件并在其中插入數(shù)據(jù)的話。jxls-reader允許你用一個(gè)簡(jiǎn)單的XML文件描述解析規(guī)則,讀取Excel文件和你的各種JAVA對(duì)象(population of yourJava objects)的所有其他工作都會(huì)自動(dòng)完成环疼。
二.JXLS安裝
為了使用JXLS引擎习霹,你必須把jxls-core.jar添加到項(xiàng)目的classpath,如果計(jì)劃使用JXLS來讀取.xls文件炫隶,那么你必須還要把jxls-reader.jar加入到項(xiàng)目的classpath中淋叶。
如果你用Maven來構(gòu)建你的應(yīng)用程序,你可以在你的pom.xml文件中配置指定要求的JXLS模塊的依賴伪阶,讓它們可以從Maven倉(cāng)庫下載煞檩。
下面的Apache類庫也要求添加到項(xiàng)目的classpath中
●POI 3.6 or higher
●Commons BeanUtils
●Commons Collections
●Commons JEXL
●Commons Logging
●Commons Digester
注意:當(dāng)前JXLS版本可能無法正常地與較早的POI庫版本工作,因此栅贴,如果你必須要使用較早版本的POI(prior3.2)使用較老版本的JXLS就行了
三.JXLS參考
1.簡(jiǎn)介
這部分描述在.xls模板文件的中的對(duì)象屬性訪問語法斟湃,如果想讓JXLS引擎進(jìn)行正確的處理.xls模板文件就必須使用規(guī)定的語法。
接下來的部分假設(shè)我們有兩個(gè)相互依賴的JAVA beans檐薯,類型分別為Department和Employee凝赛,在代碼中像這樣被傳遞到XLSTransformer中:
Departmentdepartment;
...//initialization
Map beans =new HashMap();
beans.put("department",department);
XLSTransformertransformer = new XLSTransformer();
transformer.transformXLS(xlsTemplateFileName,beans, outputFileName);
- 屬性訪問
2.1.基本屬性訪問
使用下面的語句來訪問Excel單元格中簡(jiǎn)單的bean屬性:
${department.name}
在上面這個(gè)語句中,JXLS引擎會(huì)通過關(guān)鍵字department在當(dāng)前bean映射下搜索這個(gè)bean,然后會(huì)嘗試獲取這個(gè)bean的name屬性的值并把它放到相應(yīng)的Excel單元格中坛缕。
同理墓猎,我們可以訪問更加復(fù)雜的屬性,例如赚楚,要輸出這個(gè)department中的屬性chief中的name屬性的值毙沾,我們可以用:
${department.chief.name}
訪問任何深度的對(duì)象屬性都是可以的。例如
${bean.bean1.bean2.bean3.bean4.bean5.bean6.bean7.bean8.bean9.beanX.property1}
2.2多個(gè)屬性在一個(gè)單元格中
在一個(gè)單元格直晨,我們可以連接幾個(gè)屬性搀军。例如:
Employee:${employee.name} - ${employee.age} years
這樣,我們得到的輸出是:
Employee: John -35 years
其中${employee.name}的值是John,同理${employee.age}的值是35.
3.使用標(biāo)簽
JXLS允許在模板中使用預(yù)定義的XML標(biāo)簽來控制XLS轉(zhuǎn)換行為勇皇。
3.1 jx:forEach標(biāo)簽
<jx:forEach>標(biāo)簽的典型用法如下:
<jx:forEach items="${departments}" var="department" >
${department.name}| ${department.chief}
</jx:forEach>
jx標(biāo)簽可以相互嵌套使用
如果你把jx:forEach標(biāo)簽的開始標(biāo)簽和結(jié)束標(biāo)簽放在同一行的話罩句,JXLS會(huì)在同一行上重復(fù)在jx:forEach標(biāo)簽的開始標(biāo)簽和結(jié)束標(biāo)簽之間的Excel單元格。
目前敛摘,如果你想要用jx:forEach標(biāo)簽重復(fù)Excel的行门烂,那么你必須把jx:forEach標(biāo)簽的開始標(biāo)簽和結(jié)束標(biāo)簽放在不同的行,把要重復(fù)的行包含在中間兄淫,jx:forEach標(biāo)簽所在行的所有單元格都會(huì)被忽略屯远。
以下是一個(gè)實(shí)例的截圖:
3.1.2 forEach標(biāo)簽的數(shù)據(jù)分組
jx:forEach標(biāo)簽可以通過一個(gè)底層bean的屬性對(duì)數(shù)據(jù)集合的分組,這可以通過jx:forEach標(biāo)簽的groupBy and groupOrder屬性完成捕虽,groupBy用于指定一個(gè)屬性進(jìn)行分組慨丐,groupOrder用于指定各個(gè)分組排列順序。例如:
<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>
在這個(gè)例子中泄私,我們把employees按age屬性分組房揭,當(dāng)遇到groupBy屬性的時(shí)候备闲,JXLS內(nèi)部執(zhí)行分組并且放置名為group的新的bean到上下文中,這個(gè)新的bean是很簡(jiǎn)單-它包含兩個(gè)屬性:item屬性和items屬性捅暴,item屬性是分組中當(dāng)前處理的bean恬砂;items屬性
代表這個(gè)分組中所有bean的集合。
正如你看到的蓬痒,在這個(gè)例子中我們首先使用下列語句顯示一些關(guān)于當(dāng)前分組age屬性的信息
Age:${group.item.age}
之后泻骤,我們使用內(nèi)部<jx:forEach>標(biāo)簽來實(shí)現(xiàn)迭代并顯示所有分組中的記錄
<jx:forEachitems="${group.items}" var="employee">
${employee.name}| ${employee.payment} | ${employee.bonus}
</jx:forEach>
默認(rèn)情況下,如果沒有g(shù)roupOrder屬性組的順序?qū)⒈话础霸取钡捻樞虮A粑嗌荩员氵@些分組的排列順序跟原先的集合中一樣狱掂,如果你需要按升序或是降序排列排列這些分組,那么你可以將groupOrder屬性相應(yīng)地設(shè)置為ASC或DESC
在使用groupBy屬性的情況下粹断,jx:forEach標(biāo)簽的var屬性將被忽略
3.1.3forEach標(biāo)簽的篩選功能
你可以用jx:forEach標(biāo)簽的‘select’屬性來選擇把哪些記錄包含在循環(huán)中符欠,例如,如果我們想只包含工資高于 2000元的員工瓶埋,我們可以使用下面的語句:
<jx:forEach items="${employees}" var="employee" select="${employee.payment > 2000}">
${employee.name}| ${employee.payment} | ${employee.bonus}
</jx:forEach>
3.14 forEach標(biāo)簽的varStatus 屬性
jx:forEach標(biāo)簽支持varStatus屬性希柿,varStatus屬性用來定義一個(gè)循環(huán)狀態(tài)的名字,在每一次迭代中养筒,循環(huán)狀態(tài)對(duì)象會(huì)被傳遞到bean上下文曾撤。循環(huán)狀態(tài)對(duì)象是LoopStatus類的一個(gè)實(shí)例,LoopStatus類有一個(gè)單一(靜態(tài))的'index'屬性用來確定當(dāng)前記錄在集合中的索引值(索引值從0開始)晕粪。
<jx:forEachitems="${employees}" var="employee"varStatus="status">
${status.index}|${employee.name}|${employee.payment}|${employee.bonus}
</jx:forEach>
3.2 jx:if標(biāo)簽
典型的<jx:if>標(biāo)簽的用法如下:
<jx:if test="${department.chief.payment > 2000.0}">
Chief Name: ${department.chief.name}
</jx:if>
jx:if標(biāo)簽可以基于某些條件來排除某些行或是某些列挤悉,如果你把jx:if標(biāo)簽的開始標(biāo)簽和結(jié)束標(biāo)簽放在同一行的話,JXLS會(huì)根據(jù)test的條件來處理或刪除包含在標(biāo)簽提內(nèi)的列巫湘;如果你把jx:if標(biāo)簽的開始標(biāo)簽和結(jié)束標(biāo)簽放在不同行的話装悲,JXLS會(huì)根據(jù)test的條件來處理或刪除包含在標(biāo)簽提內(nèi)的行。
3.3 jx:outline標(biāo)簽
<jx:outline>標(biāo)簽可以將特定的行組成一組尚氛。例如:
<jx:outline>標(biāo)記有一個(gè)可選的布爾類型的屬性“detail”聲明結(jié)果初始化的狀態(tài)-它們應(yīng)該展開顯示還是折疊顯示诀诊,默認(rèn)值是false即分組的行會(huì)被折疊顯示(隱藏)
3.4 jx:out標(biāo)簽
<jx:out>標(biāo)簽的用法如下:
<jx:out expr="expr
這個(gè)標(biāo)簽可以在任何地方使用,表達(dá)式將正常使用阅嘶,只是將JXLS表達(dá)式作為屬性值属瓣。
例如:
ession" />
- 執(zhí)行SQL查詢
在許多企業(yè)級(jí)應(yīng)用中,Excel報(bào)表起著非常重要的作用讯柔,現(xiàn)在JXLS可以直接吧SQL查詢語句直接寫入.xls模板文件中抡蛙,這樣在模板進(jìn)行轉(zhuǎn)換的時(shí)候,SQL語句會(huì)被執(zhí)行魂迄,所有的查詢結(jié)果都會(huì)正確地填入Excel報(bào)表中粗截。
要執(zhí)行SQL查詢,并在Excel文件中顯示查詢結(jié)果捣炬,你必須在模板進(jìn)行轉(zhuǎn)換之前把一個(gè)特殊的bean寫入bean上下文慈格,這個(gè)特殊的bean要實(shí)現(xiàn)ReportManager接口怠晴。目前這個(gè)接口只有一個(gè)方法:
public Listexec(String sql) throws SQLException
這個(gè)方法的參數(shù)是一個(gè)SQL查詢語句,執(zhí)行結(jié)果浴捆,返回一個(gè)list(list的泛型是bean)
JXLS對(duì)這個(gè)接口提供了一個(gè)默認(rèn)的實(shí)現(xiàn),叫做ReportManagerImpl稿械,ReportManagerImpl使用owSetDynaClass來把ResultSet對(duì)象封裝到對(duì)象集合中选泻,下面是這個(gè)類的用法:
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構(gòu)造函數(shù)將數(shù)據(jù)庫連接對(duì)象和bean的map作為參數(shù)傳遞給XLSTransformer,然后把ReportManager對(duì)象放到bean上下文中使用“rm”作為key美莫。這意味著页眯,我們可以執(zhí)行任何SQL查詢語句通過把它作為參數(shù)傳遞給rm.exec()方法,例如:
${rm.exec("SELECTname, age FROM employee")}
通常情況下厢呵,這個(gè)語句和jx:forEach標(biāo)簽結(jié)合起來使用來遍歷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標(biāo)簽的groupBy屬性來實(shí)現(xiàn)按某些列的值進(jìn)行分組查詢。
以下是一個(gè)實(shí)例:
. . .//主要代碼
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();
}//. . .
4.2 依賴查詢
如果使用jdbc的數(shù)據(jù)庫驅(qū)動(dòng)話襟铭,你可以給ReportManagerImpl傳遞任何SQL語句碌奉,你可以把在一個(gè)查詢中使用另一個(gè)查詢的結(jié)果,你可以把一個(gè)查詢的結(jié)果放到bean上下文中寒砖,當(dāng)在依賴查詢需要使用這個(gè)結(jié)果的時(shí)候就可以使用了赐劣。依賴查詢(子查詢)典型的用法可以用在兩個(gè)jx:forEach標(biāo)簽,當(dāng)其中一個(gè)標(biāo)簽嵌套在令一個(gè)標(biāo)簽里面的時(shí)候哩都,下面是一個(gè)簡(jiǎn)單的例子:
<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魁兼,然后在內(nèi)部的jx:forEach標(biāo)簽中使用
<jx:forEachitems="${rm.exec('SELECT name, age, payment, bonus, birthDate FROMemployee e where e.depid = ' + dep.id)}" var="employee">
JXLS會(huì)替換掉當(dāng)前處理的部門的id值以便于能查詢到當(dāng)前部門的所有員工
4.3 查詢語句包含參數(shù)
前面的例子已經(jīng)顯示了如何使用內(nèi)部SQL查詢參數(shù),此外漠嵌,我們還可以使用外部參數(shù)咐汞,如果我們把它放在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,下面我們使用它來構(gòu)建一條查詢語句:
<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查詢語句中使用單引號(hào)化撕。
4.4 JDBC結(jié)果集
雖然JXLS沒有為JDBC查詢結(jié)果集設(shè)計(jì)的數(shù)據(jù)出口,但是查詢結(jié)果集可以使用Commons BeanUtils的動(dòng)態(tài)類很容易進(jìn)行輸出挺身,XLSTransformer有兩種方法可以導(dǎo)出結(jié)果集中的數(shù)據(jù)侯谁,第一種使用org.apache.commons.beanutils.RowSetDynaClass,第二種是基于類net.sf.jxls.report.ResultSetCollection章钾。
4.4.1基于RowSet的輸出
當(dāng)你構(gòu)建一個(gè)org.apache.commons.beanutils.RowSetDynaClass類的實(shí)例的時(shí)候墙贱,底層數(shù)據(jù)被復(fù)制到內(nèi)存中的動(dòng)態(tài)bean集合中,這個(gè)集合就代表結(jié)果贱傀,所以你可以馬上關(guān)閉結(jié)果集惨撇,通常當(dāng)你處理實(shí)際數(shù)據(jù)之前,結(jié)果集就已經(jīng)關(guān)閉了府寒,這種方法的缺點(diǎn)就是你必須消耗一定的性能和內(nèi)存來復(fù)制這些結(jié)果數(shù)據(jù)魁衙。
使用這種方法首先你必須構(gòu)建一個(gè)新的RowSetDynaClass的實(shí)例报腔,并把結(jié)果集傳遞給它。
ResultSetresultSet = ...
RowSetDynaClassrowSet = new RowSetDynaClass(resultSet, false);
第二個(gè)構(gòu)造函數(shù)的參數(shù)表示屬性名稱在動(dòng)態(tài)bean的結(jié)果集中不應(yīng)該小寫剖淀。在你初始化RowSetDynaClass的實(shí)例之后纯蛾,你可以調(diào)用它的getRows()方法來獲得動(dòng)態(tài)bean的結(jié)果集,并用通常的方法輸出纵隔。
Map beans = newHashMap();
beans.put("employee", rowSet.getRows() );
XLSTransformertransformer = new XLSTransformer();
transformer.transformXLS(templateFileName, beans, destFileName);
下面是一個(gè)實(shí)例:
…//
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();//…
4.4.2 結(jié)果集輸出
如果你不想在內(nèi)存中加載所有的結(jié)果集數(shù)據(jù)翻诉,并同意在處理數(shù)據(jù)的時(shí)候一直保持?jǐn)?shù)據(jù)庫連接不斷開的話,你可以使用net.sf.jxls.report.ResultSetCollection類捌刮,這個(gè)類以ResultSet(結(jié)果集)為參數(shù)并實(shí)現(xiàn)了Collection接口來操作底層數(shù)據(jù),反過來ResultSetCollection使用org.apache.commons.beanutils.ResultSetDynaClass來返回檢索到的數(shù)據(jù)作為動(dòng)態(tài)對(duì)象碰煌。
下面是net.sf.jxls.report.ResultSetCollection 類的使用:
ResultSetCollectionrsc = new ResultSetCollection(resultSet, false);
beans.put("employee", rsc );
第二個(gè)構(gòu)造函數(shù)的參數(shù),表明屬性名稱在處理之前不應(yīng)該小寫绅作。
下面是一個(gè)實(shí)例:
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();不然會(huì)出現(xiàn)異常(對(duì)只轉(zhuǎn)發(fā)結(jié)果集的無效操作: last "的異常)應(yīng)該使用Statement stmt=conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
分析:異常出現(xiàn)于移動(dòng)結(jié)果集的指針時(shí),原因是在生成statement對(duì)象的時(shí)候提供的參數(shù)不同無參數(shù)的那個(gè)方法使用的是默認(rèn)參數(shù),statement執(zhí)行后得到的結(jié)果集類型為 ResultSet.TYPE_FORWARD_ONLY.這種類型的結(jié)果集只能通過rs.next();方法逐條讀取,使用其他方法就會(huì)報(bào)異常. 如果想執(zhí)行一些復(fù)雜的移動(dòng)結(jié)果集指針的操作就要使用其他參數(shù)了
4.5 在報(bào)表中插入圖表
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);
源碼文件和普通的文件一樣芦圾。
在Excel模板中插入圖表,在工具欄選插入-----圖表俄认,然后選擇圖表的類型个少,有柱狀圖,餅狀圖等等梭依,插入后稍算,選中圖表右擊選擇“選擇數(shù)據(jù)”然后對(duì)圖表中的數(shù)據(jù)進(jìn)行設(shè)置,(橫軸和水平軸)插入圖表有一個(gè)缺陷役拴,圖表中的數(shù)據(jù)條數(shù)要固定糊探,而且要預(yù)先設(shè)置好,不能在運(yùn)行的時(shí)候確定河闰,然后如果數(shù)據(jù)量太大的話科平,顯示會(huì)很擁擠。