JasperReport报表导出踩坑实录
写在最前面
翻了翻博客,因为太忙,已经好久没认真总结过了。
正好趁着今天老婆出门团建的机会,记录下最近这段时间遇到的大坑-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); //自定义sheet名称 exporter.setParameter(JRXlsExporterParameter.SHEET_NAMES, sheetNames);
JRExporterParameter.JASPER_PRINT_LIST,传入一个listJasperPrint的集合,每个JasperPrint即一个Sheet页。
sheetNames 为自定义的数组类型,如:String[] sheetNames = {"自定义1","自定义2","自定义3"};如不需要也可以不配置此项。
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、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.
解决方法:
1、把需要用到的字体(可以直接拷贝windows系统的C:\WINDOWS\Fonts 下的相关字体)拷贝当前项目的classpath下,一般为classes目录下
2、在classpath里添加 jasperreports.properties 属性文件
文件内容为:
net.sf.jasperreports.awt.ignore.missing.font=true
7、大数据内存溢出和内存泄露问题!!
这里需要说一下,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 。
❤本博客只适用于研究学习为目的,大多为学习笔记,如有错误欢迎指正,如有误导敬请谅解(本人尽力保证90%的验证和10%的猜想)。
❤如果这篇文章对你有一点点的帮助请给一份推荐! 谢谢!你们的鼓励是我继续前进的动力。