JasperReport報表導出踩坑實錄 JAVA實用案例之文件導入導出(POI方式)

寫在最前面

翻了翻博客,由於太忙,已經很久沒認真總結過了。html

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

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

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

JasperReport是個什麼東西?

這貨其實在國內用戶也很多,是個國外的產品,並且能夠說在JAVA報表領域應用是至關的普遍。windows

我當初剛剛接觸這個報表的時候仍是至關的喜歡的,最主要的是它的可視化工具,真的是讓我欲罷不能,居然能夠經過簡單畫圖的方式來設計JAVA報表。提及畫圖就是能夠經過可視化的工具,讓咱們可視化的設計報表模板,而且它支持輸出的文件格式很普遍,包括EXCEL、WORD、PDF、HTML、XML、CSV等等。api

看起來是否是很強大,一次設計,屢次複用。固然強大得的東西,每每都有兩面性,這不就被我遇到了,折磨了我至關長的時間,後文會詳細描述的。數組

JasperReport的大胸弟

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

具體的工做流程:tomcat

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

下圖,就是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的版本一致。

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

  一、 加載模板生成Jasperreport對象

  二、利用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的問題

一、兩行前的空白

若是你使用上面的代碼導出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);

二、數據量很大,title屢次寫入

若是你一個Sheet數據不少,可能會遇到表頭屢次打印的狀況,這種狀況下,你須要加上高度設置。

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

三、Cell的類型的問題

有時候咱們導出的Excel報表,須要使用Excel的函數計算,若是全都是文本格式,天然計算不了,這種狀況下,咱們須要使用

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

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

四、多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);
//自定義sheet名稱
exporter.setParameter(JRXlsExporterParameter.SHEET_NAMES, sheetNames);
JRExporterParameter.JASPER_PRINT_LIST,傳入一個listJasperPrint的集合,每一個JasperPrint即一個Sheet頁。
sheetNames 爲自定義的數組類型,如:String[] sheetNames = {"自定義1","自定義2","自定義3"};如不須要也能夠不配置此項。

五、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"

六、Linux下字體缺失問題

報錯內容:Font '微軟雅黑' is not available to the JVM. See the Javadoc for more details.
net.sf.jasperreports.engine.util.JRFontNotFoundException: Font '微軟雅黑' is not available to the JVM. See the Javadoc for more details.

解決方法: 
一、把須要用到的字體(能夠直接拷貝windows系統的C:\WINDOWS\Fonts 下的相關字體)拷貝當前項目的classpath下,通常爲classes目錄下 
二、在classpath裏添加 jasperreports.properties 屬性文件
文件內容爲: 
net.sf.jasperreports.awt.ignore.missing.font=true 

七、大數據內存溢出和內存泄露問題!!

這裏須要說一下,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 

相關文章
相關標籤/搜索