6.2 普通报表打印方案
6.2.1 打印PDF报表方案
在开发管理软件时,经常需要数据生成报表打印,但Web打印效果并不好,况且也不美观,所以通常采用生成PDF报表进行打印,打印PDF报表有很多种方法,常见的是使用iText组件或iReport组件,iText组件设计需要在页面中使用程式画出表格、单元格等,而iReport组件则使用设计和数据分离的原理设计报表,本节将分别使用这两种方法介绍打印PDF报表方案。
1.方案分析
不论是使用何种方法使报表以PDF格式输出,都需要从数据库中取数据放入PDF中,所以首先要建立数据库连接,为了方便传值,建立一个与数据表相对应的JavaBean,使用setXXX()和getXXX()方法取出数据库中的数据放入List中,如果是使用iText组件,需要创建iText实例,然后创建流,把数据库中的数据以流的形式写入文档中,关闭文档。如果使用iReport组件,需要在iReport中设计报表格式,保存为pdf.jrxml,在JSP中使用JasperCompileManager类的compileReportToFile()静态方法实现编译,以流的形式读取为PDF格式显示。iReport是一个手动制作报表的软件,JSP若想调用iReport制作完成的报表,需要使用JasperReport组件,本节对JaspeRrport不做介绍,在以下的高级报表打印方案中会有详细讲解。
为了使读者更好的理解本方案,给出了流程图,其中图6.10为使用iText组件实现PDF报表打印方案具体流程图,图6.11为使用iReport 组件实现PDF报表打印方案具体流程图。

图6.10 使用iText实现PDF报表打印流程图

图6.11 使用iReport实现PDF报表打印流程图
2.实施过程
在开发网络购物后台管理系统时,通常会有对商品详细信息的报表打印操作,有时候客户会要求使用PDF格式输出打印报表,在这种情况下,就可以使用到PDF报表打印,如图6.12所示。

图6.12 网络购物系统商品信息报表
生成PDF报表有两种方法。
l 方法一 使用iText 组件生成PDF报表

首先创建商品信息表tb_iText,如表6.1所示。
表6.1 tb_iText 表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序号
|
not null
|
name
|
varchar(10)
|
商品名称
|
null
|
number
|
varchar(20)
|
商品数量
|
null
|
bron
|
varchar(20)
|
商品产地
|
null
|
为了使Java可以连接SQL Server 2000,将3个驱动包放入项目路径下的WEB-INF/lib目录中,同时为了可以使用iText组件,将iText-2.0.4.jar包放入项目路径下的WEB-INF/lib目录中。
为了使数据库数据表的数据保存到集合中,需要一个JavaBean,命名为ProductInfo.java,该类的属性与数据表中字段的名称一一对应,并实现getXXX()和SettXXX()方法。部分程序代码如下:
例程6-1 代码位置:光盘/mr/6/6.2/6.2.1/01/src/com/wsy/ProductInfo.java
public String getBron() {
return bron;
}
public void setBron(String bron) {
this.bron = bron;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
为了在添加中文时不至于每次都复写相同代码,创建一个名为PDFParagraph.java的类,该类继承了Paragraph,用于设置中文输出,PDFParagraph.java定义了字体的类型、大小。设置中文为12号字体。部分程序代码如下:
例程6-2 代码位置:光盘/mr/6/6.2/6.2.1/01/src/com/wsy/PDFParagraph.java
BaseFont bfChinese = BaseFont.createFont("STSong-Light",
"UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
FontChinese = new Font(bfChinese, 12, Font.NORMAL);
此时需要一个数据库连接的类,名为JDBConnection.java,其中包括数据库连接、查询、插入、关闭等操作,将连接数据库代码封装在一个类中的目的是继承面向对象的思想,致使代码高效重用性,如果开发系统改换数据库的用户名、密码,乃至改换数据库,只要修改本类中的代码就可以轻松应对,但如果连接数据库代码写入JSP页面,要修改的部分就繁多了很多,而且后台代码和前台页面显示代码混在一起不利于后期维护。JDBConnection.java主要程序代码如下:

例程6-3 代码位置:光盘/mr/6/6.2/6.2.1/01/src/com/wsy/JDBConnection.java
private final String dbDriver = "com.microsoft.jdbc.SQL Server 2000.SQLServerDriver";
private final String url = "jdbc:microsoft:SQL Server 2000://localhost:1433;DatabaseName=db_FABD06";
private final String userName = "sa";
private final String password = "";
private Connection con = null;
//构造函数进行数据库连接
public JDBConnection() {
try {
Class.forName(dbDriver).newInstance();
con = DriverManager.getConnection(url, userName, password);
con.setAutoCommit(true);
} catch (Exception ex) {
e.getMessage();
}
}
//查询数据库库,返回ResultSet
public ResultSet executeQuery(String sql) {
ResultSet rs;
try {
Statement stmt = con.createStatement();
try {
rs = stmt.executeQuery(sql);
} catch (SQLException e) {
System.out.println(e.getMessage());
return null;
}
} catch (SQLException e) {
System.out.println(e.getMessage());
System.out.println("executeQueryError!");
return null;
}
return rs;
}
//查询结果放入集合List中
public List selectProductList() {
List list = new ArrayList();
ProductInfo info = null;
String sql = "select * from tb_iText";
ResultSet rs = this.executeQuery(sql);
try {
while (rs.next()) {
info = new ProductInfo();
info.setId(Integer.valueOf(rs.getString(1)));
info.setName(rs.getString(2));
info.setNumber(rs.getString(3));
info.setBron(rs.getString(4));
list.add(info);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
万事具备,只欠东风,下面只需使用JSP页面调用PDF报表即可。创建名称为iTextDatabaseTest.jsp页面文件,该页面的主要功能是将查询数据表中的信息显示在PDF报表,该页面的主要程序代码如下:
例程6-4 代码位置:光盘/mr/6/6.2/6.2.1/01/iTextDatabaseTest.jsp
//表头进行设置
String title[] = { "编号", "姓名", "数量", "产地" };
PdfPTable table = new PdfPTable (title.length);//根据字段个数定义表格的列数
PdfPCell cell = null;
for (int i = 0; i < title.length; i++) {//根据要显示的字段个数创建单元格,并把字段名称写入单元格中
cell = new PdfPCell ();
cell.addElement(new PDFParagraph(title[i]));
table.addCell(cell);
}
//表体设置
JDBConnection connection = new com.JDBConnection();
List list = connection.selectProductList();//调用数据表中的内容存放在List对象中
for (int j = 0; j < list.size(); j++) {//以集合的个数进行循环行数
ProductInfo info = (ProductInfo) list.get(j);//取出数据写入单元格中
table.addCell(info.getId().toString());
table.addCell(new PDFParagraph(info.getName()));
table.addCell(info.getNumber());
table.addCell(new PDFParagraph(info.getBron()));
}
%>
l 方法二 使用iReport组件生成PDF报表

准备工作 :本示例使用软件:iReport-2.0.0、SQL Server 2000。
源码文件夹放入了iReport-2.0.0安装软件,报表制作原件以及编译后生成PDF文档。读者也可以自行下载iReport最新版本。
首先把SQL Server 2000的驱动包放入iReport所在目录的lib中,读者使用不同的数据库应将相应的驱动包放入lib中。
(1)打开iReport 2.0.0新建报表文档,配置数据源,具体操作可参看6.1.3节。
(2)选择“Data”→“报表查询”输入“select * from tb_iText”,如图6.13所示。

图6.13 iReport输入sql语句
(3)放置列标题和数据。
单击菜单栏中的
,在报表编辑器输入列标题,注意放入报表标题区(columnHeader区域)。

将Fields中的三个字段拖入细节区(detail区域)中,detail区域是循环显示字段的,如图6.14所示。

图6.14 iReport设计商品信息表
(4)单击菜单栏中的
快捷键,查看报表结果。

3.补充说明
使用JDBC方式连接数据库,执行selectProductList()方法后,一定要调用数据库关闭方法,也就是及时关闭数据库,否则当有多个数据库连接时,会产生问题,如同使用Oracle数据库插入、修改、删除表内容时需要commit一样。
本节只是演示了iReport组件最为基本的报表,除了上述报表形式,iReport组件还支持分组、主从、交叉、套打等很多形式的报表。会在下面的章节中进行介绍。另外,IReport组件还支持多种文件形式输出,可以在“建立”→“HTML预览”选择所有生成的格式,同时也可以在“Options”→“选项”中选择报表动态编译完成后的存入的位置。
6.2.2 打印表格与图像方案
表格和图像在报表中占据着非常重要的位置,现在越来越多的应用系统要求图像和表格同时打印,使信息数据显示的更为详细和形象,所以其应用日趋广泛。
1.方案分析
在这里笔者选择使用iText组件处理表格和图片,使用了处理表格的类com.lowagie.text.Table以及处理图片的类com.lowagie.text.Image。具体细节操作可查阅6.1.1节表格处理以及图片处理相关内容。在本方案中笔者采用打印简历的方式演示表格和图片的输出,简历中包括表格和图片以及个人相关信息,表格和图片可以使用iText组件画出,具体信息则需要取出数据表中的数据添加到单元格中,为了使读者更好地理解本方案,下面给出具体流程,如图6.15所示。

图6.15 简历输出流程图
2.实施过程
在开发ERP系统中,企业都会存储一些求职者的电子简历,有时需要以PDF报表的形式输出打印,这样既美观又实用,在这种情况下,就可以应用到表格与图片打印,如图6.16所示。

图6.16 在JSP页面生成PDF,输出简历格式

首先建立求职者个人信息表,数据结构如表6.2所示。
表6.2 表格tb_jianli 简历表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
name
|
varchar(20)
|
姓名
|
null
|
zy
|
varchar(20)
|
专业
|
null
|
xb
|
varchar(20)
|
性别
|
null
|
xl
|
varchar(10)
|
学历
|
null
|
nl
|
varchar(10)
|
年龄
|
null
|
dh
|
varchar(20)
|
电话
|
null
|
yx
|
varchar(20)
|
邮箱
|
null
|
jybj
|
varchar(80)
|
教育背景
|
null
|
gzjy
|
varchar(50)
|
工作经验
|
null
|
qt
|
varchar(50)
|
其他
|
null
|
zhaopian
|
varchar(20)
|
照片名称
|
null
|
如同6.2.1节中的PDF报表输出方案,为了可以使用iText组件,首先把itext-2.0.4.jar放入项目目录下的WEB-INF/lib路径中,然后创建名称为PDFParagraph.java的类文件,该类主要设置PDF报表的字体,字体大小以及中文显示,实现代码具体如下:
例程6-5 代码位置:光盘/mr/6/6.2/6.2.2/03/src/com/wsy/DFParagraph.java
public class PDFParagraph extends Paragraph {
public PDFParagraph(String content) {
super(content, getChineseFont()); //通过构造方法实现字体定义功能
}
//设置转型字体的方法
BaseFont bfChinese = BaseFont.createFont("STSong-Light",
"UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
FontChinese = new Font(bfChinese, 12, Font.NORMAL);
在上述代码中,PDFParagraph.java继承了Paragraph类,通过构造方法中的super()方法,将传入的参数,通过getChineseFont()方法进行转型。
为了方便取数据库中的数据,首先把数据库连接以及查询表放入一个类中,需要一个映射表结构的JavaBean,这个JavaBean 除了setXXX(),getXXX()方法之外,还有一个getRecord()方法,该方法的返回值类型是集合Collection。部分程序代码如下:
例程6-6 代码位置:光盘/mr/6/6.2/6.2.2/03/src/com/wsy/Jlbb.java
public Collection getRecord(){
Collection ret=new ArrayList();
String sql="select * from tb_jianli";
ConnDB conn=new ConnDB();
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Jlbb j=new Jlbb();
j.setId(rs.getString("id"));
j.setName(rs.getString("name"));
j.setZy(rs.getString("zy"));
j.setXb(rs.getString("xb"));
j.setXl(rs.getString("xl"));
j.setNl(rs.getString("nl"));
j.setDh(rs.getString("dh"));
j.setYx(rs.getString("yx"));
j.setJybj(rs.getString("jybj"));
j.setGzjy(rs.getString("gzjy"));
j.setQt(rs.getString("qt"));
j.setZhaopian(rs.getString("zhaopian"));
ret.add(j);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
}
return ret;
}
在JSP中通过<jsp:useBean>指令来使用Jlbb.java,由于返回值为Collection,所以可以使用迭代函数读取集合中的数据,此时需要把迭代函数的it转型为JavaBean的形式,程序代码如下:
例程6-7 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
<jsp:useBean id="t" scope="page" class="com.Jlbb"/>
Collection ret=t.getRecord();
Iterator it=ret.iterator();
while(it.hasNext()){
Jlbb j=(Jlbb)it.next();
设置好了输出字体,便可以在JSP中画简历表格了,根据输出简历的效果,选择7行,10列的表格,设置周边显示边框,部分程序代码如下:
例程6-8 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
Table table = new Table(7,10);//建立一个7列10行的表格
table.setBorderWidth(1);//设置边框宽为1
table.setPadding(1);//设置单元格间距为1
然后开始创建单元格,设置表头,使表头单元格向右扩展7列,将单元格添加到表格中,在设置表头信息之后,必须调用endHeaders()方法,否则跨页时,表头信息不会在第二页显示,程序代码如下:
例程6-9 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
Cell cell = new Cell(new PDFParagraph("个人简历"));//设置表头名称
cell.setHeader(true);//将该单元格作为表头显示
cell.setColspan(7);//设置表头占7列
table.addCell(cell);//把单元格添加到表格中
table.endHeaders();///*要注意的是一旦表头信息添加完了之后,必须调用endHeaders()方法,否则当表格跨页后,表头信息不会再显示*/
表头创建完成后,开始创建简历体部分的单元格,填入文字和从数据表中取出的值,程序代码如下:
例程6-10 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
cell=new Cell(new PDFParagraph("姓名"));
table.addCell(cell);
cell=new Cell(new PDFParagraph(j.getName()));
cell.setColspan(2);
table.addCell(cell);
cell=new Cell(new PDFParagraph("专业"));
table.addCell(cell);
cell=new Cell(new PDFParagraph(j.getZy()));
cell.setColspan(2);
table.addCell(cell);
最关键的部分是放置图片,首先把需要显示的图片放入Tomcat/Webapps/projectname,程序方可找到要加载的图片。数据表中存放的是照片的名称,此处取出名称,转型成Image格式。加载到单元格中,向下扩3列,程序代码如下:
例程6-11 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
String filePath=pageContext.getServletContext().getRealPath(j.getZhaopian());
Image jpg = Image.getInstance(filePath);
cell=new Cell(jpg);
cell.setRowspan(3);
table.addCell(cell);
添加“教育背景”时侯注意cell.disableBorderSide(1)方法,此方法是使单元格的某个边不显示,感兴趣的读者可自行查阅iText组件的API,程序代码如下:
例程6-12 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
cell=new Cell(new PDFParagraph("教育背景"));
cell.disableBorderSide(1);
cell.setRowspan(2);
cell.setColspan(1);
table.addCell(cell);
cell=new Cell(new PDFParagraph(j.getJybj()));
cell.setColspan(6);
cell.setRowspan(3);
table.addCell(cell);
cell=new Cell(new PDFParagraph(""));
cell.disableBorderSide(1);
cell.setColspan(1);
table.addCell(cell);
最后以PDF流的形式输出,方法如下:
例程6-13 代码位置:光盘/mr/6/6.2/6.2.2/03/iTextTest.jsp
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
PdfWriter.getInstance(document, buffer);
document.open();
document.add(table);
document.close();
out.clear();
out = pageContext.pushBody();
DataOutput output = new DataOutputStream(response.getOutputStream());
byte[] bytes = buffer.toByteArray();
response.setContentLength(bytes.length);
for (int i = 0; i < bytes.length; i++) {
output.writeByte(bytes[i]);
}
3.补充说明
如果设置了一个n行的表格头,但最终结果表格仅仅n行或者还不够n行的情况下,表格会出错,导致整个表格无法写到文档中。
有两点读者必须注意:
(1)在iText组件没有行的概念,一个表格里面直接放单元格,如果一个3列的表格中放进6个单元格的话,那么是两行的表格。
(2)如果一个3列的表格放入5个最基本的单元格,就会出错。
在报表中,有时候会碰到非常大的表格,如果希望在浏览第二页时,仍能够看到表头就需要用到表头的设置了,简单的调用PdfPTable类实例的setHeaderRows()方法就可以设定表头有几行了。例如设置头两行为表头,代码如下。
table.setHeaderRows(2);//设置了头两行为表格头
6.2.3 打印Excel报表方案
在向用户提供表格形式的数据时,如果以Excel表格显示数据,不但给用户一个熟悉的工作环境,而且用户可以根据自己的需要处理表格中的数据。
使用Windows操作系统的读者对Excel(电子表格)一定不会陌生,但是要使用Java语言来操纵Excel文件并不是一件容易的事。在Web应用日益盛行的今天,通过Web来操作Excel文件的需求越来越强烈,目前较为流行的操作是在JSP或Servlet中创建一个CSV (comma separated values)文件,并将这个文件以MIME,text/csv类型返回给浏览器,接着浏览器调用Excel并且显示CSV文件。这样只是说可以访问到Excel文件,但是还不能真正的操纵Excel文件。本节笔者使用两种开源组件操控Excel文件。
1.方案分析
打印Excel报表笔者在这里使用两种方法,即POI组件和Java Excel组件:
利用POI组件将数据库中表数据写入Excel文档和使用iText组件流程基本相同,首先连接数据库,进行表查询,将数据结果集存入List中,实例化一个HSSFWorkbook对象,进行行、单元格的设置,将从数据库中读出的数据以流的形式写入到Excel文档中,关闭流。具体过程如图6.17所示。

图6.17 使用POI组件生成Excel表方案分析流程图
通过开源项目Java Excel访问Excel,循环遍历Excel单元格,利用JSP的内置对象out将内容输出到JSP页面的Excel单元格中。在JSP页面设置容器类型为application/vnd.ms-excel,并调用readExcel()方法。利用Java Excel访问Excel具体流程如图6.18所示。

图 6.18 使用Java Excel组件生成Excel文件
2.实施过程
在开发物资管理系统中,用户通常要求把数据导出到Excel中,在Excel中方便处理数据。在这种情况下就可以应用到打印Excel报表,如图6.19、6.20所示。

图6.19 物资管理系统

图6.20 生成Excel文件
这里首先创建一个物资详细信息表,如表6.3所示。
表6.3 表格tb_poitest 简历表
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
rnum
|
int(4)
|
登记号
|
null
|
regionid
|
int(4)
|
注册号
|
null
|
cname
|
varchar(20)
|
产品中文名称
|
null
|
ename
|
varchar(20)
|
产品英文名称
|
null
|
cfdx
|
varchar(50)
|
产品服务对象
|
null
|
cpgnms
|
varchar(100)
|
产品功能描述
|
null
|
cplb
|
varchar(20)
|
产品类别
|
null
|
实现数据导出到Excel文件有两种方法。
l 方法一 通过POI组件实现

这里将连接数据库的代码封装到一个Java类中,由于此类与前面讲解代码相同,所以本节不再赘述。
首先将POI组件中的jar包放入项目中路径下的WEB-INF/lib目录中,然后便可以在JSP中使用POI组件了。
为了添加工作表,必须创建一个HSSFSheet实例,使用POI组件中的createSheet("sheetname")创建工作表,根据显示内容大小创建指定大小的单元格。程序代码如下:
例程6-14 代码位置:光盘/mr/6/6.2/6.2.3/04/PoiTest.jsp
<%
if(rsRow!=null){
HSSFWorkbook wb=new HSSFWorkbook();
HSSFSheet sheet=wb.createSheet("poiexample"); //建立名为poiexample的表
//给工作表定义8个列宽
sheet.setColumnWidth((short)0,(short)2500);
sheet.setColumnWidth((short)1,(short)6000);
sheet.setColumnWidth((short)2,(short)3500);
sheet.setColumnWidth((short)3,(short)9000);
sheet.setColumnWidth((short)4,(short)8000);
sheet.setColumnWidth((short)5,(short)8000);
sheet.setColumnWidth((short)6,(short)20000);
sheet.setColumnWidth((short)7,(short)8000);
创建第一行,将第一行设置8列,为每个单元格设置UTF-16编码,以防出现乱码。在第一行的单元格添加字段名称,程序代码如下:
例程6-15 代码位置:光盘/mr/6/6.2/6.2.3/04/PoiTest.jsp
HSSFRow row=sheet.createRow(0);
HSSFCell cell[]=new HSSFCell[8];
for(short i=0;i<8;i++){
cell[i]=row.createCell(i);
cell[i].setEncoding(HSSFCell.ENCODING_UTF_16); //将单元格定义成UTF_16编码,这样才能使输出数据不会乱码
}
cell[0].setCellValue("登记ID");
...//在单元格中写入文字
为了将从数据库中取出的数据放入List,创建JavaBean,这个JavaBean除了setXXX()和getXXX()方法之外,还有一个根据用户需要显示的行数从数据表中取值的ReadRecord(int rsRow)方法,它返回的值类型是集合List,程序代码如下:
例程6-16 代码位置:光盘/mr/6/6.2/6.2.3/04/src/com/wsy/PoiTest.java
public static List ReadRecord(int rsRow){
Conn conn=new Conn();
String sql="select * from tb_poitest where id<='"+rsRow+"'";
ResultSet rs=conn.executeQuery(sql);
List l=new ArrayList();
try{
while(rs.next()){
POITest p=new POITest();
p.setId(rs.getString("id"));
p.setRnum(rs.getString("rnum"));
p.setRegionid(rs.getString("regionid"));
p.setCname(rs.getString("cname"));
p.setEname(rs.getString("ename"));
p.setCfdx(rs.getString("cfdx"));
p.setCpgnms(rs.getString("cpgnms"));
p.setCplb(rs.getString("cplb"));
l.add(p);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
}
return l;
}
在JSP显示从数据库中取出的值需要引用JavaBean,由于返回值类型为List,所以根据list.size()方法返回的个数创建行数,与字段对应,每行的单元格数都为8个,list.get(i)取出数据库中值写入单元格中,同样需要设置编码,这样才不会出现乱码,程序代码如下:
例程6-17 代码位置:光盘/mr/6/6.2/6.2.3/04/PoiTest.jsp
List l=poi.ReadRecord(Integer.parseInt(rsRow));
if(l.size()>0&&l!=null){
for(int i=0;i<l.size();i++){
POITest p2=(POITest)l.get(i);
HSSFRow datarow = sheet.createRow(i + 1);
HSSFCell data[] = new HSSFCell[8];
for (short j = 0; j < 8; j++) {
data[j] = datarow.createCell(j);
//将单元格定义成UTF_16编码,这样才能使输出数据不会乱码
data[j].setEncoding(HSSFCell.ENCODING_UTF_16);
}
...//在单元格中写入从数据库中取出的值
}
}
由于用户在表单中选择了时间,所以把时间插入到Excel单元格中,但是插入的时间必须格式化,在Excel文件中才会正常显示,"m/d/yy h:mm"代表“月/日/年 小时/分钟”,由于要把时间添加到显示数据表格的下一行,所以创建sheet.createRow(l.size()+1)这行,添加单元格,最后将格式化的时间写入单元格,用setCellStyle(cellStyle)方法把样式附加到单元格上,创建Excel文件,程序代码如下:
例程6-18 代码位置:光盘/mr/6/6.2/6.2.3/04/PoiTest.jsp
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy h:mm"));
HSSFRow datarow = sheet.createRow(l.size()+1);
HSSFCell datacell2 = datarow.createCell((short)0);
datacell2.setCellValue("添加时间:");
HSSFCell datacell = datarow.createCell((short)1);
datacell.setCellValue(time);
datacell.setCellStyle(cellStyle);
File file = new File("d://workbook.xls");
%>
最后一步创建输出流,写入文件,关闭流,程序代码如下:
例程6-19 代码位置:光盘/mr/6/6.2/6.2.3/04/PoiTest.jsp
try {
FileOutputStream fileOut = new FileOutputStream(file);
wb.write(fileOut);
fileOut.close();
} catch (IOException e) {
e.printStackTrace();
}
l 方法二 通过Java Excel组件实现

使用Java Excel组件基本上与通过POI组件实现此方案是相同的,不同的是采用提交到Servlet方式实现,需要将表单提交到excelservlet,程序代码如下:
例程6-20 代码位置:光盘/mr/6/6.2/6.2.3/05/PoiTest.jsp
<form action="excelservlet" name="form1" method="post">
...//表格标签省略
</form>
若使系统找到提交的位置,需要在WEB-INF/web.xml中加入如下语句:
例程6-21 代码位置:光盘/mr/6/6.2/6.2.3/05/WebRoot/WEB-INF/web.xml
<display-name>OpenExcel</display-name>
<servlet>
<servlet-name>excelservlet</servlet-name>
<servlet-class>mrgf.ExcelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>excelservlet</servlet-name>
<url-pattern>/excelservlet</url-pattern>
</servlet-mapping>
在Servlet的doGet()方法中,取出用户在poiTest.jsp提交的时间和所要导出的行数,根据行数查询结果集,程序代码如下:
例程6-22 代码位置:光盘/mr/6/6.2/6.2.3/05/src/com/wsy/ExcelServlet.java
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType("application/vnd.ms-excel");
// 获得查询结果集
String rsRow=request.getParameter("rsRow");
String time=request.getParameter("time");
OperateDatabase db = new OperateDatabase();
db.conndb("db_FABD06", "sa", "");
ResultSet rs = db.select("select * from tb_poitest where id<='"+rsRow+"'");
根据ResultSetMetaData这个类中的getColumnCount()方法取出表的列数,赋予到一个变量中,程序代码如下:
例程6-23 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
int columnCount = 0;
ResultSetMetaData rsmd = null;
try {
rsmd = rs.getMetaData();
columnCount = rsmd.getColumnCount();
} catch (SQLException e) {
System.out.println("在获取结果集信息时抛出异常,内容如下:");
e.printStackTrace();
}
首先添加表头信息,将“商品信息表”字样以Label格式写入第一行第一列。程序代码如下:
例程6-24 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
// 填写表头
Label label = null;
label=new Label(0,0,"商品信息表");
try {
sheet.addCell(label);
} catch (WriteException e) {
System.out.println("在填写表头时抛出异常,内容如下:");
e.printStackTrace();
}
把字段名称放入数组中,程序代码如下:
例程6-25 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
// 获取字段名称
String[] fieldName = new String[columnCount];
String a[]={"序号","登记号","所在城市ID","产品中文名","产品英文名","产品服务对象","产品功能描述","产品类别"};
for (int i = 0; i < columnCount; i++) {
try {
fieldName[i]=a[i];
} catch (Exception e) {
}
}
添加表体,从数据库中取出数据放入List中,程序代码如下:
例程6-26 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
List notes = new ArrayList();
try {
while (rs.next()) {
List note = new ArrayList();
for (int i = 1; i <= columnCount; i++) {
note.add(rs.getString(i));
}
notes.add(note);
}
} catch (SQLException e) {
System.out.println("在获取记录信息时抛出异常,内容如下:");
e.printStackTrace();
}
创建工作簿,程序代码如下:
例程6-27 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
WritableWorkbook writbook = null;
try {
writbook = Workbook.createWorkbook(response.getOutputStream());
} catch (IOException e) {
System.out.println("在创建工作簿时抛出异常,内容如下:");
e.printStackTrace();
}
添加工作表,程序代码如下:
例程6-28 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
// 创建工作表并指定名称和索引位置
WritableSheet sheet = writbook.createSheet("Sheet1", 0);
数据表中的数据用迭代函数取出,放入Lable中,写入到单元格中,之所以设置row=2,column=0是因为起始添加的位置在第3行,第一列,根据循环累加列,依次写入数据,当第3行添加完毕时,列清零,行累加,保证下一行依然从第一列添加,程序代码如下:
例程6-29 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
// 填写记录
int row = 2;
int column = 0;
Iterator itNotes = notes.iterator();
while (itNotes.hasNext()) {
Iterator itNote = ((List) itNotes.next()).iterator();
while (itNote.hasNext()) {
label = new Label(column, row, itNote.next().toString());
try {
sheet.addCell(label);
} catch (WriteException e) {
System.out.println("在填写记录时抛出异常,内容如下:");
e.printStackTrace();
}
column = column + 1;
}
column = 0;
row = row + 1;
}
最后把提交的时间添加到工作表中,程序代码如下:
例程6-30 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
//添加时间
label = new Label(0,row,time);
try{
sheet.addCell(label);
}catch(Exception e){
e.printStackTrace();
}
写入文件,为了释放内存,需要手动关闭文件,程序代码如下:
例程6-31 代码位置:光盘/mr/6/6.2/6.2.3/05/ExcelServlet.java
// 写入文件
writbook.write();
// 关闭工作簿对象,释放内存空间
try {
writbook.close();
} catch (Exception e) {
System.out.println("在关闭工作簿时抛出异常,内容如下:");
e.printStackTrace();
}
3.补充说明
POI为了解决中文出现乱码的问题,设置编码,代码如下:
cell[i].setEncoding(HSSFCell.ENCODING_UTF_16);
Java Excel组件可以设置一些样式,如日期样式,居中显示,代码如下:
// 定义日期格式
DateFormat dtFormat = new DateFormat(dateType);
WritableCellFormat dateFormat = new WritableCellFormat(noteFont,
dtFormat);
// 设置居中显示
try {
dateFormat.setAlignment(jxl.format.Alignment.CENTRE);
} catch (WriteException e) {
System.out.println("在设置居中显示时抛出异常,内容如下:");
e.printStackTrace();
}
6.2.4 打印Word报表方案
Microsoft Word是微软提供的文字处理软件,Word处理文档的便利和人性化得到了多方人士的认可,Word适用于制作各种文档,如信函、传真、公文、报刊、书刊和简历等,当然,Word同样也适用于制作报表。
1.方案分析
由于Word支持HTML格式,所以可以先在Word中做好模板,另存为Web页面,然后将HTML文件修改为JSP文件,进行操作。
为了更好的理解Word打印方案,首先来看一下流程图,如图6.21所示。

图6.21 打印Word报表流程图
也可以通过JavaScript的ActiveXObject()构造函数创建一个OLE Automation(ActiveX)对象的实例,并应用该实例的相关方法来实现。
ActiveXObject()构造函数的一般语法格式如下:
var objectVar = new ActiveXObject(class[, servername]);
参数说明:
objectVar:用于指定引用对象的变量。
class:用于指定应用程序的名字或包含对象的库,并且指定要创建的对象类的类型。采用library.object的语法格式,如“Word.Application”,则表明要创建的是Word对象。
servername:可选参数,用于指定包含对象的网络服务器的名字。
使用ActiveObject()方法实现Word报表方案的流程图,如图6.22所示。

图6.22 打印Word报表流程图
2.实施过程
在开发游戏网后台管理系统时,通常需要保留浏览者注册相关信息,为了方便后台管理者的操作,需要把数据打印导入到Word中进行打印操作,在这种情况下,就可以使用到Word打印报表,如图6.24所示。

图6.23 游戏网注册界面

图6.24 打印Word报表
实现数据导入Word有两种方法。
l 方法一 应用HttpServletResponse对象的setContentType()方法设置

首先从数据库中取出数据,利用JavaBean的setXXX()方法把数据存入List中,程序代码如下:
例程6-32 代码位置:光盘/mr/6/6.2/6.2.4/06/src/com/wsy/WordTest.java
public List ReadRecord(){
Conn conn=new Conn();
String sql="select * from wordtest order by id";
ResultSet rs=conn.executeQuery(sql);
List l=new ArrayList();
try{
while(rs.next()){
WordTest t=new WordTest();
...//把数据存入JavaBean中
l.add(t);
}
}catch(Exception e){
e.printStackTrace();
}
return l;
}
}
在Word中填加下列表格,如表6.4所示。
表6.4 生成Word报表
用户名
|
昵称
|
性别
|
密码
|
qq号码
|
电话
|
地址
|
guest
|
路人甲
|
boy
|
123456
|
123456
|
130********
|
吉林省长春市*街**道
|
guest
|
路人乙
|
gril
|
123456
|
123456
|
130********
|
吉林省长春市*街**道
|
另存为Web格式,为test.html,然后修改为test.jsp,当然,客户端自身也要有Word软件才可以打开。保存后的JSP部分程序代码如下:
例程6-33 代码位置:光盘/mr/6/6.2/6.2.4/06/test.jsp
<%@ page contentType="application/msword;charset=GBK" %>
<%@ page import="Java.sql.*" %>
<%@taglib uri="/WEB-INF/mytag.tld" prefix="mytag" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:w="urn:schemas-microsoft-com:office:word"
xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv=Content-Type content="text/html; charset=GB2312">
<meta name=ProgId content=Word.Document>
<meta name=Generator content="Microsoft Word 9">
<meta name=Originator content="Microsoft Word 9">
<link rel=File-List href="./test/filelist.xml">
<title>用户名</title>
<!--[if gte mso 9]><xml>
为了使JSP界面没有逻辑代码,在这里使用自定义标签技术,创建一个displayTag.java类作为自定义标签类,作显示内容之用。displayTag.java继承了TagSupport类,重写了doEngTag()方法,可以在displayTag.java中实例化out和request等内置对象,这样,自定义类就可以像Servlet一样把一些内容显示在JSP上,调用JavaBean的ReadRecord()方法,返回值为List类型,根据JavaBean的getXXX()方法打印数据库数据表中取出的值,部分程序代码如下。
例程6-34 代码位置:光盘/mr/6/6.2/6.2.4/06/src/com/wsy/displayTag.java
public class displayTag extends TagSupport{
public int doEndTag() throws JSPException{
JSPWriter out=pageContext.getOut();
HttpServletRequest request=(HttpServletRequest)pageContext.getRequest();
WordTest w=new WordTest();
List l=w.ReadRecord();
try{
if(l.size()>0&&!l.isEmpty()){
for(int i=0;i<l.size();i++){
WordTest w2=(WordTest)l.get(i);
out.println("<tr>");
out.println("<td width=189 valign=top style='width:142.0pt;border:none;border-bottom:solid windowtext .5pt;padding:0cm 5.4pt 0cm 5.4pt'>");
。。。写出循环打印表格
return super.doEndTag();
}
}
若想在JSP界面引用自定义标签,首先需要在WEB-INF目录下创建mytag.tld标签文件,<name></name>之间代表标签的名字,<tagclass></tagclass>之间代表自定义标签类所在位置,关键程序代码如下:
例程6-35 代码位置:光盘/mr/6/6.2/6.2.4/06/WebRoot/WEB-INF/mytab.tld
<tag>
<name>display</name>#自定义标签名字
<tagclass>com.wsy.displayTag</tagclass>#自定义标签所在位置。
<bodycontent>empty</bodycontent>
<info>A demo</info>
</tag>
然后在test.jsp文件加入如下程序代码,就可以在test.jsp引用自定义标签了。
例程6-36 代码位置:光盘/mr/6/6.2/6.2.4/06/test.jsp
<%@taglib uri="/WEB-INF/mytag.tld" prefix="mytag" %> #在jsp中声明自定义标签。
在欲显示表格的位置加入自定义标签,就正常显示表格了,程序代码如下:
例程6-37 代码位置:光盘/mr/6/6.2/6.2.4/06/test.jsp
<mytag:display/>在jsp页面中引用自定义标签。
l 方法二 通过JavaScript的ActiveXObject()构造函数来实现

不论是使用哪种方式实现Word报表打印,都需要连接数据库,从数据表中取值,依然使用方法一中的Conn.java类封装数据库连接代码,使用WordTest.java这个JavaBean作为传值的媒介,displayTag.java作为自定义标签类,代码如方法一所示,本方法不再赘述。
使用WordTest.java取出数据库中数据后,使用自定义标签在JSP显示后,制作一个“打印”连接,这个连接调用了一个叫做“outDoc()”的事件。程序代码如下:
例程6-38 代码位置:光盘/mr/6/6.2/6.2.4/07/WordTest.jsp
<p align="center"><a href="#" onClick="outDoc();">打印</a></p>
为了使用JavaScript打印Word报表,赋予需要打印的表格一个id="customer"的变量。程序代码如下:
例程6-39 代码位置:光盘/mr/6/6.2/6.2.4/07/WordTest.jsp
<table id="customer" border=1 align="center" cellpadding=0 cellspacing=0>
outDoc()主要代码通过JavaScript的ActiveXObject()构造函数创建一个OLEAutomation ActiveX实例,本例是“Word Application”,实例化对象名称为wdapp,使用Add()方法添加新文档,分别取出要打印表格的行数和列数,根据行列数循环将表格数据放入数组中,在第一行第一列添加表头“客户信息表”,插入表格,最后保存名为customerList.doc的Word文档,使用Printout()输出文档,将实例化对象wdaap置空。JavaScript程序代码如下:
例程6-40 代码位置:光盘/mr/6/6.2/6.2.4/07/WordTest.jsp
<script language="Javascript">
function outDoc(){
var table=document.all.customer;
row=table.rows.length;
column=table.rows(1).cells.length;
var wdapp=new ActiveXObject("Word.Application");
wdapp.visible=true;
wddoc=wdapp.Documents.Add(); //添加新的文档
thearray=new Array();
//将页面中表格的内容存放在数组中
for(i=0;i<row;i++){
thearray[i]=new Array();
for(j=0;j<column;j++){
thearray[i][j]=table.rows(i).cells(j).innerHTML;
}
}
var range = wddoc.Range(0,0);
range.Text="客户信息列表"+"/n";
wdapp.Application.Activedocument.Paragraphs.Add(range);
wdapp.Application.Activedocument.Paragraphs.Add();
rngcurrent=wdapp.Application.Activedocument.Paragraphs(3).Range;
var objTable=wddoc.Tables.Add(rngcurrent,row,column)//插入表格
for(i=0;i<row;i++){
for(j=0;j<column;j++){
objTable.Cell(i+1,j+1).Range.Text = thearray[i][j].replace(" ","");
}
}
wdapp.Application.ActiveDocument.SaveAs("customerList.doc",0,false,"",true,"",false,false,false,false,false);
wdapp.Application.Printout();
wdapp=null;
}
</script>
3.补充说明
实施过程中使用了自定义标签,在JSP页面中使用自定义标签可以简化JSP页面代码量,使逻辑代码和前台代码充分分离,有助于页面的后期维护,自定义标签继承了TagSupport类,重写了父类方法doEndTag(),可以实例化一个out,out.write()将数据输出到页面中。同时,需要在WEB-INF中建立*.tld文件,这样可在JSP中引用标签文件。