java学习之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的大数据方案还是挺不错的。

(0)

相关推荐

  • java学习之JasperReport踩坑

    下面进入正题,来介绍下今天的猪脚JasperReport或者叫它ireport亦或jasperstudio,当然后面两个是它的可视化工具. JasperReport是个什么东西? 这货其实在国内用户也不少,是个国外的产品,而且可以说在JAVA报表领域应用是相当的广泛. 我当初刚刚接触这个报表的时候还是相当的喜欢的,最主要的是它的可视化工具,真的是让我欲罢不能,竟然可以通过简单画图的方式来设计JAVA报表.说起画图就是可以通过可视化的工具,让我们可视化的设计报表模板,并且它支持输出的文件格式很广泛

  • java使用BeanUtils.copyProperties踩坑经历

    1. 原始转换 提起对象转换,每个程序员都不陌生,比如项目中经常涉及到的DO.DTO.VO之间的转换,举个例子,假设现在有个OrderDTO,定义如下所示: public class OrderDTO { private long id; private Long userId; private String orderNo; private Date gmtCreated; // 省略get.set方法 } 有个OrderVO,定义如下所示: public class OrderVO { pr

  • Java中Objects.equals踩坑记录

    目录 前言 1. 案发现场 2. 判断相等的方法 2.1 使用==号 2.2 使用equals方法 3. 空指针异常 4. Objects.equals的作用 5. Objects.equals的坑 总结 前言 最近review别人代码的时候,发现有个同事,在某个业务场景下,使用Objects.equals方法判断两个值相等时,返回了跟预期不一致的结果,引起了我的兴趣. 原本以为判断结果会返回true的,但实际上返回了false. 记得很早之前,我使用Objects.equals方法也踩过类似的

  • Java切割字符串的踩坑实战记录

    目录 坑出现的环境 问题的解决 补充:java分割字符串常见语法 一.java.lang.String.split() 二.java.util.StringTokenizer() 总结 坑出现的环境 一般情况下切割字符串会使用split或者StringTokenizer,如下代码 String s = ",,o,,"; String[] split = s.split(","); 期望得到数组["","","o&qu

  • Java十道入门易踩坑题分析后篇

    目录 一,写在前面 二,代码分析 代码分析① 代码分析② 代码分析③ 代码分析④ 代码分析⑤ 代码分析⑥ 代码分析⑦ 代码分析⑧ 代码分析⑨ 代码分析⑩ 一,写在前面 本篇主要讲类和对象这一章节的踩坑题,这一章也相对复杂和难理解,面试也是常考的一类题,我分析的都是比较经典的,读者觉得自己学习的不够扎实,可以收藏,如果觉得写发不错的话,求个免费的赞,谢谢! 二,代码分析 代码分析① 阅读如下代码. 请问,对语句行 test.hello(). 描述正确的有() package NowCoder; c

  • Java Bean转Map的那些踩坑实战

    目录 一.背景 二.那些坑 2.0 测试对象 2.1 JSON 反序列化了类型丢失 2.1.1 问题复现 2.2.2 问题描述 2.2 BeanMap 转换属性名错误 2.2.1 commons-beanutils 的 BeanMap 2.2.2 使用 cglib 的 BeanMap 三.解决办法 3.1 解决方案 3.2 原理解析 四.总结 一.背景 有些业务场景下需要将 Java Bean 转成 Map 再使用. 以为很简单场景,但是坑很多. 二.那些坑 2.0 测试对象 import lo

  • Java 热更新 Groovy 实践及踩坑指南(推荐)

    目录 Groovy 是什么? Java 为何需要 Groovy ? 热部署技术设计及实现 使用场景 风控安全——规则引擎 监控中心 活动营销 技术实现 脚本加载/更新 脚本执行 生产踩坑指南 Java8 lambda 与 Groovy 语法问题 GroovyClassLoader 加载机制导致频繁gc问题 脚本首次执行耗时高 Groovy 是什么? Apache的Groovy是Java平台上设计的面向对象编程语言.这门动态语言拥有类似Python.Ruby和Smalltalk中的一些特性,可以作

  • Java踩坑记录之Arrays.AsList

    前言 java.util.Arrays的asList方法可以方便的将数组转化为集合,我们平时开发在初始化ArrayList时使用的比较多,可以简化代码,但这个静态方法asList()有几个坑需要注意: 一. 如果对集合使用增加或删除元素的操作将会报错 如下代码: List list = Arrays.asList("a","b","c"); list.add("d"); 输出结果: Exception in thread &q

  • Java踩坑记录之BigDecimal类

    前言 在java.math包中提供了对大数字的操作类,用于进行高精确计算,如BigInteger,BigDecimal类.而平常我们开发中使用最多的float和double只能适用于一般的科学和工程计算,如果要在比较精确的计算方面如货币,那么使用float和double会相应的丢失精度,因此用于精密计算大数字的类BigDecimal就必不可少了.所以BigDecimal适合商业计算场景,用来对超过16位有效位的数进行精确的运算.但是BigDecimal的使用并不像float和double那样,使

  • Java List的remove()方法踩坑

    目录 1.普通for循环遍历List删除指定元素--错误!!! 2.for循环遍历List删除元素时,让索引同步调整--正确! 3.倒序遍历List删除元素--正确! 4.foreach遍历List删除元素--错误!!! 5.迭代删除List元素--正确! 6.迭代遍历,用list.remove(i)方法删除元素--错误!!! 7.List删除元素时,注意Integer类型和int类型的区别. 总结: Java的List在删除元素时,一般会用list.remove(o)/remove(i)方法.

随机推荐