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