下面進(jìn)入正題,來(lái)介紹下今天的豬腳JasperReport或者叫它ireport亦或jasperstudio,當(dāng)然后面兩個(gè)是它的可視化工具。
JasperReport是個(gè)什么東西?
這貨其實(shí)在國(guó)內(nèi)用戶也不少,是個(gè)國(guó)外的產(chǎn)品,而且可以說(shuō)在JAVA報(bào)表領(lǐng)域應(yīng)用是相當(dāng)?shù)膹V泛。
我當(dāng)初剛剛接觸這個(gè)報(bào)表的時(shí)候還是相當(dāng)?shù)南矚g的,最主要的是它的可視化工具,真的是讓我欲罷不能,竟然可以通過(guò)簡(jiǎn)單畫圖的方式來(lái)設(shè)計(jì)JAVA報(bào)表。說(shuō)起畫圖就是可以通過(guò)可視化的工具,讓我們可視化的設(shè)計(jì)報(bào)表模板,并且它支持輸出的文件格式很廣泛,包括EXCEL、WORD、PDF、HTML、XML、CSV等等。
看起來(lái)是不是很強(qiáng)大,一次設(shè)計(jì),多次復(fù)用。當(dāng)然強(qiáng)大得的東西,往往都有兩面性,這不就被我遇到了,折磨了我相當(dāng)長(zhǎng)的時(shí)間,后文會(huì)詳細(xì)描述的。
JasperReport的大胸弟
前面我說(shuō),JasperReport或者叫它ireport或jasperstudio,其實(shí)這是不準(zhǔn)確的。二弟ireport、三弟jasperstudio其實(shí)是jasper的輔助視覺設(shè)計(jì)工具,你不用它也能設(shè)計(jì)jasper報(bào)表,多寫點(diǎn)XML白。5.5之前這個(gè)工具叫ireport,5.5之后隨著三弟jasperstudio的出生,ireport就被完全替代了,其實(shí)這兩個(gè)工具基本上是一樣的,一奶同胞。
具體的工作流程:
①首先Jasper會(huì)獲取需要輸出的格式信息的xml文件,然后從xml文件中編譯出.jasper類型的文件,然后這個(gè)jasper文件可以在我們的應(yīng)用程序中被加載生成最終的報(bào)表。有沒有很熟悉的感覺,是的,這一點(diǎn)和java很像,都需要編譯一下。
下圖,就是ireport的操作界面,jasperstudio類似,就不貼了,大家可以自行百度下。
上圖每種類型的band簡(jiǎn)單介紹一下。
(1)Title band:title段只在整個(gè)報(bào)表的第一頁(yè)的最上面部分顯示,除了第一頁(yè)以外,不管報(bào)表中共有多少個(gè)頁(yè)面也不會(huì)再出現(xiàn)Title band中的內(nèi)容。
(2)pageHeader Band:顧名思義,pageHeader 段中的內(nèi)容將會(huì)在整個(gè)報(bào)表中的每一個(gè)頁(yè)面中都會(huì)出現(xiàn),顯示在位置在頁(yè)面的上部,如果是報(bào)表的第一頁(yè),pageHeader 中的內(nèi)容將顯示在Title Band下面,除了第一頁(yè)以外的其他所有頁(yè)面中pageHeader中的內(nèi)容將在顯示在頁(yè)面的最上端。
(3)pageFooter Band:顯示在所在頁(yè)面的最下端。
(4)lastPageFooter Band:顯示在最后一頁(yè)的最下端。
(5)Detail Band:報(bào)表內(nèi)容段,在這個(gè)Band 中設(shè)計(jì)報(bào)表中需要重復(fù)出現(xiàn)的內(nèi)容,Detail 段中的內(nèi)容每頁(yè)都會(huì)出現(xiàn)。
(6)columnHeader Band:針對(duì)Detail Band的表頭段,一般情況下在這個(gè)段中畫報(bào)表的表頭。
(7)columnFooter Band:針對(duì)Detail Band的表尾段。
(8)Summary Band:表格的合計(jì)段,出現(xiàn)在整個(gè)報(bào)表的最后一頁(yè)中的Detail band 的后面,一般用來(lái)統(tǒng)計(jì)報(bào)表中某一個(gè)或某幾個(gè)字段的合計(jì)值。
上面就是可視化的工具的全部,其實(shí)怎么用很簡(jiǎn)單,上手摸索下就會(huì)了,既然是踩坑實(shí)錄,這個(gè)自然不是重點(diǎn),不說(shuō)了。
代碼中的應(yīng)用
這是我總結(jié)的步驟,可能描述的不是很準(zhǔn)確,大家湊合下
①設(shè)計(jì)模板,生成JRXML文件,↑↑上面的可視化工具設(shè)計(jì)你所需要的模板樣式
②編譯模板,JRXML編譯成Jasper文件,就像java中的.java和.class文件一樣,程序中運(yùn)行的需要是*.jasper的二進(jìn)制文件。
其實(shí)這一步可以直接用ireport編譯生成.jasper,當(dāng)然也可以在運(yùn)行時(shí)通過(guò)jasper程序編譯。但是建議如果在程序中編譯的話,jasper版本最好和ireport或者jasperstudio的版本一致。
③執(zhí)行報(bào)表(數(shù)據(jù)填充到報(bào)表)
1、 加載模板生成Jasperreport對(duì)象
2、利用JasperFillManager,生成JasperPrint對(duì)象
④最后利用JRXlsxExporter導(dǎo)出類,將報(bào)表導(dǎo)出或者展示
加載模板
既然我們已經(jīng)利用可視化工具生成了.jasper或者.jrxml文件了,自然是需要讓程序加載它。
加載的代碼,返回jasperport對(duì)象
if (urlPath.endsWith(".jrxml")) { //compile jrxml to jasper try { InputStream is = url.openStream(); jasperReport = JasperCompileManager.compileReport(is); } catch (IOException e) { throw new BaseException("Load jasper error", e); } catch (JRException e) { throw new BaseException("The jrxml template transform to jasper file error", e); } catch (Throwable e) { log.error(e); throw new BaseException(e.getMessage()); } } else if (urlPath.endsWith(".jasper")) { try { InputStream is = url.openStream(); jasperReport = (JasperReport) JRLoader.loadObject(is); } catch (IOException e) { throw new BaseException("Load jasper error", e); } catch (JRException e) { throw new BaseException("The jrxml template file error", e); } catch (Throwable e) { log.error(e); throw new BaseException(e.getMessage()); } } else { throw new BaseException("Invalid file!"); }
獲取報(bào)表中的數(shù)據(jù)源
這里我采用javabean的方式獲取
JRDataSource dataSource = null; if (fieldValues != null && fieldValues.size() > 0) { dataSource = new JRBeanCollectionDataSource(fieldValues); } else { dataSource = new JREmptyDataSource(); }
fieldValues 為數(shù)據(jù)庫(kù)中獲取的pojo集合。
執(zhí)行報(bào)表填充
得到j(luò)asperprint對(duì)象
Map<String, Object> parameterValue = new HashMap<String, Object>(); jasperPrint = JasperFillManager.fillReport(jasperReport, parameterValue, dataSource);
最后我們利用JRXlsxExporter導(dǎo)出報(bào)表
這個(gè)也是需要配置參數(shù)最多的一個(gè)地方
baos = new ByteArrayOutputStream(); exporter = new JRXlsxExporter(); exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint); exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos); exporter.exportReport();
完成,數(shù)據(jù)已經(jīng)寫入輸出流中了,怎么輸出自己決定,是不是比其他方式代碼簡(jiǎn)介很多。
確實(shí)在代碼書寫中JasperReport有著無(wú)法比擬的優(yōu)勢(shì),各種api已經(jīng)封裝好。但是可能是恰恰做的太多,問(wèn)題也不少。
JasperReport的問(wèn)題
1、兩行前的空白
如果你使用上面的代碼導(dǎo)出EXCEL的話,你會(huì)發(fā)現(xiàn)Excel的背景是白色,沒了Excel一個(gè)個(gè)的小格子,這是因?yàn)閖asper默認(rèn)背景為白色,這樣在導(dǎo)出其他格式時(shí)也好做到兼容,當(dāng)然當(dāng)我們導(dǎo)出EXCEL并不需要。只需要加上下面兩行就可以解決。
//去除兩行之前的空白 exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,Boolean.TRUE); exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_COLUMNS,Boolean.TRUE); //設(shè)置Excel表格的背景顏色為默認(rèn)的白色 exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND,Boolean.FALSE);
2、數(shù)據(jù)量很大,title多次寫入
如果你一個(gè)Sheet數(shù)據(jù)很多,可能會(huì)遇到表頭多次打印的情況,這種情況下,你需要加上高度設(shè)置。
Field pageHeight = JRBaseReport.class.getDeclaredField( "pageHeight"); pageHeight.setAccessible(true); pageHeight.setInt(jasperReport, Integer.MAX_VALUE);
3、Cell的類型的問(wèn)題
有時(shí)候我們導(dǎo)出的Excel報(bào)表,需要使用Excel的函數(shù)計(jì)算,如果全都是文本格式,自然計(jì)算不了,這種情況下,我們需要使用
//自動(dòng)選擇格式 exporter.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);
切記,在報(bào)表設(shè)計(jì)時(shí),F(xiàn)ield字段選擇正確的類型。
4、多Sheet的問(wèn)題
我上面那個(gè)簡(jiǎn)單的例子,只是一個(gè)文件中包含一個(gè)Sheet頁(yè),假如我們的需求是一個(gè)文件導(dǎo)出多個(gè)Sheet怎么辦,別急,這個(gè)Japser早已為我們想到了。
只需要將上文中導(dǎo)出步驟換成下面這個(gè)樣子
baos = new ByteArrayOutputStream(); exporter = new JRXlsxExporter(); exporter.setParameter(JRExporterParameter.JASPER_PRINT_LIST, listJasperPrint); exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos); //設(shè)置為true,即可在一個(gè)excel中,每個(gè)單獨(dú)的jasper對(duì)象放入到一個(gè)sheet頁(yè)中 exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET,Boolean.TRUE);
JRExporterParameter.JASPER_PRINT_LIST,傳入一個(gè)listJasperPrint的集合,每個(gè)JasperPrint即一個(gè)Sheet頁(yè)。
5、Linux下啟動(dòng)不報(bào)錯(cuò),但是無(wú)法導(dǎo)出報(bào)表
其實(shí)這個(gè)問(wèn)題也困擾了我很久,后來(lái)在大佬的幫助下才想起來(lái)問(wèn)題所在,因?yàn)樗鼟伋龅母静皇莻€(gè)Exception,而是Error。我看到網(wǎng)上也有同學(xué)問(wèn)這個(gè)問(wèn)題,所以貼出來(lái)。
可以用throwable捕獲,就可以得到錯(cuò)誤信息,報(bào)錯(cuò):java.lang.InternalError: Can't connect to X11 window server using ':0.0' as
解決方法:修改tomcat/bin/catalina.sh 加JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true"
6、大數(shù)據(jù)內(nèi)存溢出和內(nèi)存泄露問(wèn)題!!
這里需要說(shuō)一下,EXCEL 03和07版的區(qū)別,03版我記得好像是只支持65532行吧,而07版之后就大的多了,具體數(shù)字我忘了,反正不是一個(gè)數(shù)量級(jí)的。
JRXlsxExporter支持導(dǎo)出xlsx文件,
JRXlsExporter則是xls的文件,很好辨認(rèn),導(dǎo)出的工具和excel的格式一樣。
然后是內(nèi)存溢出和內(nèi)存泄露問(wèn)題,這個(gè)我相信玩JAVA的朋友基本上都遇到過(guò)。
關(guān)于內(nèi)存溢出最通常的解決辦法便是增大容器的內(nèi)存,增加tomcat的內(nèi)存大小,方法大家可以百度,有很多,不重復(fù)造輪子了。
這里提醒下,如果你使用的是tomcat的話,windows安裝版,解壓縮版和Linux版的配置方式都是不同的,需要注意下。
這里我需要介紹的是JasperReport的方式,其實(shí)JasperReport是對(duì)大數(shù)據(jù)有解決方案的,在很早期的版本便推出了,JRFileVirtualizer的仿真器。
這個(gè)東西是做啥用的呢,其實(shí)它會(huì)根據(jù)你設(shè)置的參數(shù),將數(shù)據(jù)寫到硬盤的臨時(shí)文件上,這樣解決了填充報(bào)表時(shí)內(nèi)存占用過(guò)大溢出的問(wèn)題。
目前JasperReport有3個(gè)仿真器,都是用來(lái)解決這個(gè)問(wèn)題的。
分別是:
①JRFileVirtualizer
②JRSwapFileVirtualizer
③JRGzipVirtualizer
這三個(gè)仿真器又有什么區(qū)別呢?
首先是推出最早的JRFileVirtualizer,我在測(cè)試時(shí),當(dāng)導(dǎo)出30W左右的數(shù)據(jù),就會(huì)報(bào)內(nèi)存溢出,后來(lái)加上這個(gè)后就可以正常導(dǎo)出了。這個(gè)仿真器會(huì)把每一個(gè)對(duì)象生成一個(gè)臨時(shí)文件存放在硬盤上解決內(nèi)存占用的問(wèn)題,但是因?yàn)楫a(chǎn)生的臨時(shí)文件較多,無(wú)形中增加了文件創(chuàng)建和刪除的內(nèi)存消耗,所以并不是很推薦。
//寫多個(gè)文件 JRFileVirtualizer virtualizer = new JRFileVirtualizer(2, catchPath); Map<String, Object> parameterValue = new HashMap<String, Object>(); parameterValue.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); virtualizer.setReadOnly(true);
catchPath為文件緩存路徑,必須真實(shí)存在,否則會(huì)報(bào)錯(cuò)。
然后是JRSwapFileVirtualizer,這個(gè)是為了解決JRFileVirtualizer的問(wèn)題而推出的。這個(gè)仿真器,只會(huì)創(chuàng)建一個(gè)臨時(shí)文件,每個(gè)對(duì)象會(huì)占這個(gè)文件的一部分,所以就減少的文件創(chuàng)建和刪除的內(nèi)存消耗,其實(shí)這個(gè)也不是特別推薦。
//寫單個(gè)文件 RSwapFile arquivoSwap = new JRSwapFile(catchPath, 4096, 25); JRAbstractLRUVirtualizer virtualizer = new JRSwapFileVirtualizer(2, arquivoSwap, true); Map<String, Object> parameterValue = new HashMap<String, Object>(); parameterValue.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); virtualizer.setReadOnly(true);
最后是JRGzipVirtualizer這個(gè),看到Gzip,不知道你是否有聯(lián)系到壓縮這個(gè)詞匯。沒錯(cuò),這個(gè)仿真器就是使用一種特殊的壓縮算法,可以將內(nèi)存占用壓縮到二十分之一還是十分之一來(lái)著,總之很神奇。
JRAbstractLRUVirtualizer virtualizer = new JRGzipVirtualizer(2); Map<String, Object> parameterValue = new HashMap<String, Object>(); parameterValue.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); jasperPrint = JasperFillManager.fillReport(jasperReport, parameterValue, dataSource);
說(shuō)了這么多,總之就是三種仿真器解決內(nèi)存溢出問(wèn)題,我也看了很多博客里面寫利用JRFileVirtualizer,解決內(nèi)存大數(shù)據(jù)問(wèn)題。然后我在這里想說(shuō),我最最最不推薦使用JRFileVirtualizer仿真器,因?yàn)樗粌H創(chuàng)建文件消耗大,還有個(gè)很嚴(yán)重的BUG,內(nèi)存泄露!!!還有JRSwapFileVirtualizer也有這個(gè)問(wèn)題。
另外,需要說(shuō)明的是不使用仿真器,也會(huì)有內(nèi)存泄露的問(wèn)題,當(dāng)你導(dǎo)出報(bào)表后,dump出堆棧信息,會(huì)發(fā)現(xiàn)net.sf.jasperreports.engine.fill.JRTemplatePrintText類的實(shí)例特別多,無(wú)法回收,無(wú)法回收!!!并且最新版的japserreport 6.x依舊存在這個(gè)問(wèn)題,在jasper的社區(qū)和Stack Overflow存在很多這樣的問(wèn)題,而沒有解決方案。
這里推薦JRGzipVirtualizer仿真器,雖然依舊存在泄露問(wèn)題,但是因?yàn)楠?dú)特的壓縮算法,已經(jīng)將內(nèi)存泄露問(wèn)題控制在很小的范圍里了,算是一種緩解的方案吧,大概泄露的內(nèi)存占用緩解了九成以上。
總的來(lái)說(shuō),我現(xiàn)在已經(jīng)放棄這種方案了,寫出來(lái)也是為了后來(lái)的兄弟少走彎路。擼了一個(gè)POI的工具類,接下來(lái)準(zhǔn)備把所有的報(bào)表改成POI導(dǎo)出的方式,話說(shuō)POI的大數(shù)據(jù)方案還是挺不錯(cuò)的。
原文鏈接:https://www.cnblogs.com/laoyeye/p/7707149.html