1. 程式人生 > >JAVA實用案例之文件導出(JasperReport踩坑實錄)

JAVA實用案例之文件導出(JasperReport踩坑實錄)

十分 bytearray message remove 除了 只需要 老婆 不同 內存泄露問題

寫在最前面

想想來新公司也快五個月了,恍惚一瞬間。

翻了翻博客,因為太忙,也有將近五個多月沒認真總結過了。

正好趁著今天老婆出門團建的機會,記錄下最近這段時間遇到的大坑-JasperReport。

六月份的時候寫過一篇利用poi文件導入導出的小Demo,JAVA實用案例之文件導入導出(POI方式)

雖然簡單,但是企業應用的原理基本上也就是這樣,只不過是封裝的更好些,不像我之前寫的那樣每個Cell都需要定義,其實poi的方式也是我目前最推崇的方式之一了。主要原因是jxl不支持xlsx,JasperReport坑又太大,哎。下面進入正題,來介紹下今天的豬腳JasperReport或者叫它ireport亦或jasperstudio,當然後面兩個是它的可視化工具。

JasperReport是個什麽東西?

這貨其實在國內用戶也不少,是個國外的產品,而且可以說在JAVA報表領域應用是相當的廣泛。

我當初剛剛接觸這個報表的時候還是相當的喜歡的,最主要的是它的可視化工具,真的是讓我欲罷不能,竟然可以通過簡單畫圖的方式來設計JAVA報表。說起畫圖就是可以通過可視化的工具,讓我們可視化的設計報表模板,並且它支持輸出的文件格式很廣泛,包括EXCEL、WORD、PDF、HTML、XML、CSV等等。

看起來是不是很強大,一次設計,多次復用。當然強大得的東西,往往都有兩面性,這不就被我遇到了,折磨了我相當長的時間,後文會詳細描述的。

JasperReport的大胸弟

前面我說,JasperReport或者叫它ireport或jasperstudio,其實這是不準確的。二弟ireport、三弟jasperstudio其實是jasper的輔助視覺設計工具,你不用它也能設計jasper報表,多寫點XML白。5.5之前這個工具叫ireport,5.5之後隨著三弟jasperstudio的出生,ireport就被完全替代了,其實這兩個工具基本上是一樣的,一奶同胞。

具體的工作流程:

①首先Jasper會獲取需要輸出的格式信息的xml文件,然後從xml文件中編譯出.jasper類型的文件,然後這個jasper文件可以在我們的應用程序中被加載生成最終的報表。有沒有很熟悉的感覺,是的,這一點和java很像,都需要編譯一下。

下圖,就是ireport的操作界面,jasperstudio類似,就不貼了,大家可以自行百度下。

技術分享圖片

上圖每種類型的band簡單介紹一下。
(1)Title band:title段只在整個報表的第一頁的最上面部分顯示,除了第一頁以外,不管報表中共有多少個頁面也不會再出現Title band中的內容。

(2)pageHeader Band:顧名思義,pageHeader 段中的內容將會在整個報表中的每一個頁面中都會出現,顯示在位置在頁面的上部,如果是報表的第一頁,pageHeader 中的內容將顯示在Title Band下面,除了第一頁以外的其他所有頁面中pageHeader中的內容將在顯示在頁面的最上端。

(3)pageFooter Band:顯示在所在頁面的最下端。

(4)lastPageFooter Band:顯示在最後一頁的最下端。

(5)Detail Band:報表內容段,在這個Band 中設計報表中需要重復出現的內容,Detail 段中的內容每頁都會出現。

(6)columnHeader Band:針對Detail Band的表頭段,一般情況下在這個段中畫報表的表頭。

(7)columnFooter Band:針對Detail Band的表尾段。

(8)Summary Band:表格的合計段,出現在整個報表的最後一頁中的Detail band 的後面,一般用來統計報表中某一個或某幾個字段的合計值。

上面就是可視化的工具的全部,其實怎麽用很簡單,上手摸索下就會了,既然是踩坑實錄,這個自然不是重點,不說了。

代碼中的應用

這是我總結的步驟,可能描述的不是很準確,大家湊合下

①設計模板,生成JRXML文件,↑↑上面的可視化工具設計你所需要的模板樣式

②編譯模板,JRXML編譯成Jasper文件,就像java中的.java和.class文件一樣,程序中運行的需要是*.jasper的二進制文件。

其實這一步可以直接用ireport編譯生成.jasper,當然也可以在運行時通過jasper程序編譯。但是建議如果在程序中編譯的話,jasper版本最好和ireport或者jasperstudio的版本一致。

③執行報表(數據填充到報表)

  1、 加載模板生成Jasperreport對象

  2、利用JasperFillManager,生成JasperPrint對象

④最後利用JRXlsxExporter導出類,將報表導出或者展示

加載模板

既然我們已經利用可視化工具生成了.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!");
        }

獲取報表中的數據源

這裏我采用javabean的方式獲取

      JRDataSource dataSource = null;
            if (fieldValues != null && fieldValues.size() > 0) {
                dataSource = new JRBeanCollectionDataSource(fieldValues);
            } else {
                dataSource = new JREmptyDataSource();
            }
fieldValues 為數據庫中獲取的pojo集合。

執行報表填充

得到jasperprint對象

Map<String, Object> parameterValue = new HashMap<String, Object>();
jasperPrint = JasperFillManager.fillReport(jasperReport, parameterValue, dataSource);

最後我們利用JRXlsxExporter導出報表

這個也是需要配置參數最多的一個地方

baos = new ByteArrayOutputStream();
exporter = new JRXlsxExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
exporter.exportReport();

完成,數據已經寫入輸出流中了,怎麽輸出自己決定,是不是比其他方式代碼簡介很多。

確實在代碼書寫中JasperReport有著無法比擬的優勢,各種api已經封裝好。但是可能是恰恰做的太多,問題也不少。

JasperReport的問題

1、兩行前的空白

如果你使用上面的代碼導出EXCEL的話,你會發現Excel的背景是白色,沒了Excel一個個的小格子,這是因為jasper默認背景為白色,這樣在導出其他格式時也好做到兼容,當然當我們導出EXCEL並不需要。只需要加上下面兩行就可以解決。

            //去除兩行之前的空白  
            exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,Boolean.TRUE); 
            exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_COLUMNS,Boolean.TRUE); 
            //設置Excel表格的背景顏色為默認的白色  
            exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND,Boolean.FALSE);

2、數據量很大,title多次寫入

如果你一個Sheet數據很多,可能會遇到表頭多次打印的情況,這種情況下,你需要加上高度設置。

Field pageHeight = JRBaseReport.class.getDeclaredField(
                    "pageHeight");
            pageHeight.setAccessible(true);
            pageHeight.setInt(jasperReport, Integer.MAX_VALUE);

3、Cell的類型的問題

有時候我們導出的Excel報表,需要使用Excel的函數計算,如果全都是文本格式,自然計算不了,這種情況下,我們需要使用

 //自動選擇格式
 exporter.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);

切記,在報表設計時,Field字段選擇正確的類型。

4、多Sheet的問題

我上面那個簡單的例子,只是一個文件中包含一個Sheet頁,假如我們的需求是一個文件導出多個Sheet怎麽辦,別急,這個Japser早已為我們想到了。

只需要將上文中導出步驟換成下面這個樣子

baos = new ByteArrayOutputStream();
exporter = new JRXlsxExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT_LIST, listJasperPrint);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
//設置為true,即可在一個excel中,每個單獨的jasper對象放入到一個sheet頁中
exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET,Boolean.TRUE);
JRExporterParameter.JASPER_PRINT_LIST,傳入一個listJasperPrint的集合,每個JasperPrint即一個Sheet頁。

5、Linux下啟動不報錯,但是無法導出報表

其實這個問題也困擾了我很久,後來在大佬的幫助下才想起來問題所在,因為它拋出的根本不是個Exception,而是Error。我看到網上也有同學問這個問題,所以貼出來。

可以用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、大數據內存溢出和內存泄露問題!!

這裏需要說一下,EXCEL 03和07版的區別,03版我記得好像是只支持65532行吧,而07版之後就大的多了,具體數字我忘了,反正不是一個數量級的。

JRXlsxExporter支持導出xlsx文件,
JRXlsExporter則是xls的文件,很好辨認,導出的工具和excel的格式一樣。

然後是內存溢出和內存泄露問題,這個我相信玩JAVA的朋友基本上都遇到過。

關於內存溢出最通常的解決辦法便是增大容器的內存,增加tomcat的內存大小,方法大家可以百度,有很多,不重復造輪子了。

這裏提醒下,如果你使用的是tomcat的話,windows安裝版,解壓縮版和Linux版的配置方式都是不同的,需要註意下。

這裏我需要介紹的是JasperReport的方式,其實JasperReport是對大數據有解決方案的,在很早期的版本便推出了,JRFileVirtualizer的仿真器。

這個東西是做啥用的呢,其實它會根據你設置的參數,將數據寫到硬盤的臨時文件上,這樣解決了填充報表時內存占用過大溢出的問題。

目前JasperReport有3個仿真器,都是用來解決這個問題的。

分別是:

①JRFileVirtualizer

②JRSwapFileVirtualizer

③JRGzipVirtualizer

這三個仿真器又有什麽區別呢?

首先是推出最早的JRFileVirtualizer,我在測試時,當導出30W左右的數據,就會報內存溢出,後來加上這個後就可以正常導出了。這個仿真器會把每一個對象生成一個臨時文件存放在硬盤上解決內存占用的問題,但是因為產生的臨時文件較多,無形中增加了文件創建和刪除的內存消耗,所以並不是很推薦。

//寫多個文件
 JRFileVirtualizer virtualizer = new JRFileVirtualizer(2, catchPath);
 Map<String, Object> parameterValue = new HashMap<String, Object>();
 parameterValue.put(JRParameter.REPORT_VIRTUALIZER, virtualizer);
virtualizer.setReadOnly(true);
catchPath為文件緩存路徑,必須真實存在,否則會報錯。

然後是JRSwapFileVirtualizer,這個是為了解決JRFileVirtualizer的問題而推出的。這個仿真器,只會創建一個臨時文件,每個對象會占這個文件的一部分,所以就減少的文件創建和刪除的內存消耗,其實這個也不是特別推薦。

//寫單個文件
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這個,看到Gzip,不知道你是否有聯系到壓縮這個詞匯。沒錯,這個仿真器就是使用一種特殊的壓縮算法,可以將內存占用壓縮到二十分之一還是十分之一來著,總之很神奇。

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);

說了這麽多,總之就是三種仿真器解決內存溢出問題,我也看了很多博客裏面寫利用JRFileVirtualizer,解決內存大數據問題。然後我在這裏想說,我最最最不推薦使用JRFileVirtualizer仿真器,因為它不僅創建文件消耗大,還有個很嚴重的BUG,內存泄露!!!還有JRSwapFileVirtualizer也有這個問題。

另外,需要說明的是不使用仿真器,也會有內存泄露的問題,當你導出報表後,dump出堆棧信息,會發現net.sf.jasperreports.engine.fill.JRTemplatePrintText類的實例特別多,無法回收,無法回收!!!並且最新版的japserreport 6.x依舊存在這個問題,在jasper的社區和Stack Overflow存在很多這樣的問題,而沒有解決方案。

這裏推薦JRGzipVirtualizer仿真器,雖然依舊存在泄露問題,但是因為獨特的壓縮算法,已經將內存泄露問題控制在很小的範圍裏了,算是一種緩解的方案吧,大概泄露的內存占用緩解了九成以上。

總的來說,我現在已經放棄這種方案了,寫出來也是為了後來的兄弟少走彎路。擼了一個POI的工具類,接下來準備把所有的報表改成POI導出的方式,話說POI的大數據方案還是挺不錯的。

開發路上的坑,寫的不是太好還請見諒。轉載還請註明出處:小賣鋪的老爺爺 http://www.cnblogs.com/laoyeye/p/7707149.html

JAVA實用案例之文件導出(JasperReport踩坑實錄)