前言
说到Java报表工具,我接触过的只有FineReport(帆软)和JasperReports,这两个都用过,帆软是收费的而JasperReports是免费的,首先第一感觉帆软功能强大,支持导出的报表格式丰富,但是坑很多(一个格子的属性设置不对整个报表都会乱掉,比如左父格什么的,而且有时少量数据测试是没问题但数据量一大报表就乱掉了,不管什么原因引起,首先这就直接导致不容易发现问题,去年项目上线后就被这样坑过一次),再来谈谈JasperReports,开源免费,使用方面和帆软比是不如它强大,不能导Excel,通常情况下只用来导PDF,但在大多数情况下还是能满足需求的,毕竟免费的不能要求太高哈,So,本篇blog就总结一下JasperReports以及iReport的使用方法和细节。
下载iReport
地址如下:
http://community.jaspersoft.com/download
我们可以看到目前官方首推的可视化报表设计工具是Jaspersoft Studio(基于Eclipse),早期的iReport从5.5之后就不再提供更新了,但之前一直用的是iReport开发报表所以本篇blog就基于iReport这个工具进行开发,个人感觉还是蛮好用的(联想到了当年用Eclipse开发Android打死都不换Android Studio T_T),我们直接下载iReport Designer可视化编辑器即可,如下图所示:
当然还必须要有JasperReports Library才能通过Java程序来调用报表,由于只是一个jar包所以我就直接上传到CSDN供大家下载了,我使用的版本是jasperreports-5.6.1,下载地址:
http://download.csdn.net/detail/wlwlwlwl015/9509567
下载安装之后就可以使用它来创建报表文件了。
创建报表
打开iReport,点击文件–>new,可以看到一个New file面板:
可以看到左侧的菜单提供了多种报表图表类型,由于我们主要做基础报表所以只关注Report(报表)就可以了,而右侧提供了一些模板,比如纵向A4,横向A4等,通常我们用的最多就是第一个纵向A4了,选择它并点击“Open this Template”,接下来会弹出命名和定位的面板,输入报名文件名称和文件存储的目录即可:
最后点击完成即可进入报表的编辑界面了:
编辑报表
首先我们来看一下iReport的主界面:
上图是一个设置好的标准的编辑界面,左侧是inspector区域,中间是主编辑区域,右上是组件面板区域(如果没有通过Ctrl+Shift+8组合键调出),右下是属性窗口,底部是输出区域,左侧是Problem Window(报错窗口),右侧是Output Window(输出窗口),在菜单栏下面标出的是DB连接设置区域。首先我们从主编辑区域看起:
如上图,iReport把一个报表按如上图的方式进行分割,顾名思义也就是不同的区域应当放置不同的内容,操作起来的话和C#做WinForm程序的感觉差不多,就是拖控件!设属性!下面我们举例做一个成绩报表,首先是标题,一般静态文本我们统一用组件面板中的Static Text(Lable),如下图:
Title
注意一下这里最常用的两个组件就是Static Text和下面的Text Field了,Static Text通常用于放静态文本,例如:标题、列名等等,而Text Field通常用来放动态的表达式,例如数据库的查询结果,函数等等。下面从组件面板中拖动Static Text到编辑区的Title中:
如图上面的一排工具栏可以设置组件内容的对齐方式以及字体样式等等,但更多情况下我们还是会在属性面板来设置这些:
关于属性这里有非常重要的一点就是要设置报表字体和编码,将属性面板滑动到底部可以看到这两个属性:
如上图,因为我们的标题是中文,所以我们必须设置为中文字体和编码:
- STSong-Light
- UniGB-UCS2-H (Chinese Simplified)
只有这样我们在报表中才能正确显示中文,包括后面所有包含中文的控件都需要此设置。
Page Header
标题很简单,接下来是页头,我们可以在页头放一些通用属性,诸如:学校、班级信息等等,如下图所示:
我们在页头(Page Header)中放了学校、班级和时间,可以看到我们用了6个控件,3个Static Text和3个Text Filed,就像之前说的,Static Text用来放静态文本,而Text Filed用来放动态表达式,学校和班级是我们在DB中查出来的(后面再细说),而时间这里用了一个动态表达式来完成,如下图:
关于Expressions(表达式)是一个值得深入研究的话题,iReport为我们提供了多种类型的表达式,例如:
这里看到了我们非常熟悉的Java代码,Java语言也正是iReport官方推荐的第一语言(尽管还支持Groovy、Javascript语言的表达式):
iReport为我们提供了丰富的表达式语法,上图可以看到甚至包括三目表达式,可以让我们方便的进行相关操作,比如下面的设置指定格式时间并输出:
这样我们就可以把当前时间按照指定的格式显示在报表中了。
Column Header
看完了Page Header,接下来就是Column Header了,这个区域通常会放列头,所以也应当是静态文本,这个报表的表格我没有用table控件去做,而是通过多个Static Text和Text Field拼成的表格,如下图所示:
ireport自带的拖动对齐线可以方便的让我们将控件拖拽至合适的位置,包括调整它们的大小,当然也可以通过Ctrl+鼠标左键选中多个控件来为它们设置一致的属性值,关于Static Text或Text Field的边框(border)需要注意一下,因为在属性面板中并不能找到它们,我们需要选中控件然后点击鼠标右键在弹出的“Padding and Borders”中来设置边框,如下图所示:
我们依次点击Borders中的4条边,然后将Line width设置为1即可,现在我们就可以看到边框了:
Create DataSource
到此为止我们的报表中所有静态的内容基本算是完成了,接下来就是主要的动态生成的部分了,由于项目的持久层采用的是MyBatis框架,所以在这里我们选用SQL作为Query language。首先需要确认数据源,最开始已经提到了DB数据源设置的位置,点击这个图标:
之后我们会看见如下窗口:
可以看到上图就是用来创建数据源的面板,前两个是ireport提前创建好的一个空数据源和一个Sample数据源,第3个是我已经创建好的,我在这里重新再创建一个新的数据源,点击New:
可以看到上图中提供了非常丰富的数据源类型,诸如:JDBC、Hibernate、EJB等等,由于我的项目中的持久层用的是MyBatis框架,所以在这里选择第一个——Database JDBC connection即可,然后点击next:
显而易见,选择JDBC Driver并依次输入URL、Username和Password,最后点击Test,如果提示成功,那么我们的数据源就算成功创建好了,接下来就可以写报表的查询语句了。
Report Query
首先我们需要知道在哪里输入查询SQL,点击如下图标识的按钮:
之后就可以看到Query窗口了:
如上图,我们勾选Automatically Retrieve Fields之后,当我们输入完SQL语句后在下面会自动取回查询结果的所有列,同时我们在底部可以看到数据预览:
这样报表的查询SQL就算成功完成,最后我们根据列名依次把每一个放置在报表Detail 1区域的Text Field中即可。
Detail 1
没错,这个区域就是报表的主数据展示区,首先依次将Text Field拖放至Detail 1区并和列头对齐,然后设置边框,效果如下:
可以看到每个Text Field都有一个默认值$F{field},这个是什么意思呢?我们在官方文档中可以找到答案:
没错,$F是引用一个域(field)的意思,那什么又是Fields?显而易见,我们刚才写查询SQL语句返回的查询结果的每一列就是一个Field,这一点我们也可以在左侧的Inspector区中看到:
有了这些概念接下来的问题就简单了,我们只需要根据field name来一一修改每一个Text Field里的表达式即可,例如修改准考证号这一列:
下面是修改好的简单浏览一下:
有几点需要着重注意一下:
- 第一点也是最重要的一点在上图已经标出了,就是Detail 1区域的高度要和Text Field的行高保持一致,这样循环行生成报表的时候才会没有行间距。
- 所有包含中文的Text Field或Static Text都需要统一设置中文字体(STSong-Light)和中文编码(UniGB-UCS2-H (Chinese Simplified))。
- 由于我们没有用table组件而是用一个个的Text Field拼成的表格所以最好放大看一下边框细节和对齐细节等等:
- 统一设置所有Text Field和Static Text的内容对齐方式,最好是水平居中()和垂直居中()。
- 还有一个需要注意的细节就是如果SQL语句查询为空的字段如果不做处理那么默认在报表中会显示NULL,所以不太美观,但解决方案也很简单,就是将所有可能出现空值的格子(Text Field)均设置为Blank When Null,即:为null时用空格子代替:
- 最后应当在iReport中预览一下报表效果,点击Peview进入预览视图:
然后就可以看到我们的报表预览效果了:
整体还算完美吧!预览的同时注意一下底部的iReport output窗口,可以看到这样的提示信息:
没错,是对源文件report1.jrxml进行了编译而生成了report1.jasper文件,在JasperReports中,jrxml是源文件,而能运行的则是源文件编译后的jasper文件。我们预览的同时会自动编译,当然在编写源文件的同时我们也可以手动编译,编译按钮在这里:
再回过头来看预览的报表,注意到序号这一列乱掉了,由于我们的SQL是按照准考证号asc升序排列,所以用oracle的rownum做序号仿佛有些不合适,不过没关系,iReport也自带了类似的序号功能!我们通过添加变量(Variables)来实现。首先我们需要先创建一个新的变量,在左侧的Inspector区域找到Variables并new一个Variable命名为rownumber:
关于变量(Variables)我们可以在官方文档的Chapter 6中找到它的相关说明:
首先我们定义这个变量的类型为java.lang.Integer,Caculation设置为Count,Reset type设置为Report:
可以看到Reset type设置为Report时变量只会为初始化1次,并且是使用intital value expression的值作为初始值,那么我们将intital value expression设置为1即可,如下图:
最后设置变量表达式(Variable Expression)为$V{rownumber}.valueOf(1)即可。现在修改序号的Text Filed,将原来引用域改为引用变量rownumber,之后再次预览看看效果:
可以看到序号可以正常显示!至此我们的报表部分就算开发完成了,接下来看看如何在Java程序中调用报表。
java调用报表
和帆软的FineReport有些区别,帆软是将报表服务器的servlet封装在了jar包中我们只需要在web.xml中引用即可,但JasperReports就需要我们自己来写了,思路都是一样的,直接看代码:
package net.xnzz.servlet;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.util.Enumeration;
import java.util.HashMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperRunManager;
import net.xnzz.util.DBUtils;
public class JRPDFServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
Connection connection = DBUtils.getDBInstance().getInitDBConnection();
try
{
//据据jasper文件生成JasperPrint对象
ServletContext context = this.getServletConfig().getServletContext();
String fileName = request.getParameter("fileName");//ireport编译文件:*.jasper(由模板文件*.jrxml文件编译生成)
File reportFile = new File(context.getRealPath("/WEB-INF/jaspers/"+fileName));
HashMap<String, Object> parameters = new HashMap<String, Object>();//给报表模板文件传参
//得到枚举类型的参数名称,参数名称若有重复的只能得到第一个--获取页面传来的参数,和模板中文件的sql参数名称一一对应
Enumeration<?> temp = request.getParameterNames();
while (temp.hasMoreElements())
{
String paramName = (String) temp.nextElement();
String paramValue = request.getParameter(paramName);
parameters.put(paramName, paramValue);
}
byte[] bytes = JasperRunManager.runReportToPdf(reportFile.getPath(), parameters,connection);
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
ServletOutputStream out = response.getOutputStream();
out.write(bytes, 0, bytes.length);
out.flush();
out.close();
}
catch (JRException e)
{
e.printStackTrace();
}
}
}
思路很简单,就是通过IO流先读取到项目中的编译后的.jasper文件,然后再通过JasperRunManager向客户端输出一个格式为pdf的报表文件即可,由于参数和文件名等等都做了参数化的封装所以这个servlet可以供所有JasperReports复用。服务器端代码完成了,下面再看一下客户端是如何调用报表的,看一下下面这段jsp代码:
<body>
<iframe id="reportFrame" width="100%" height="88%" frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling="no" allowtransparency="yes"></iframe>
</body>
<script type="text/javascript">
function getSearch() {
var path = "${pageContext.request.contextPath}/servlet/JRPDFTYServlet?fileName=report222.jasper"
$("#reportFrame").attr("src", path);
}
很简单吧!我这里定义了一个iframe,仅仅只需要将报表servlet路径设置给iframe的src属性即可,当然如果报表有参数的话可以用js字符串拼接再串到servlet地址后面即可,到此为止所有工作就已经完成了,最后看一下程序中的报表效果。Google浏览器的效果:
IE浏览器的效果:
还不错吧~ 这样的报表用户不论是打印还是导出PDF都非常方便,关于JasperReports的介绍到此就算全部结束了,当然这只是个大概的内容,感兴趣的同学可以自己通过官方文档再进一步详细学习,下载官方文档还要注册JasperReports的会员,所以我在这里已经将下载好的官方文档上传到CSDN空间,下载地址:
总结
简单记录一下JasperReports以及它的可视化报表编辑器——iReport的使用方法,希望对这方面感兴趣的同学有所帮助, The End。