6.2 普通报表打印方案

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报表
*  实例位置光盘/mr/6/6.2/6.2.1/01
首先创建商品信息表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主要程序代码如下:
*注意:本类只是实现Java连接SQL Server 2000的示例,读者根据实际情况进行更改。
例程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报表
*  实例位置光盘/mr/6/6.2/6.2.1/02
准备工作 :本示例使用软件: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,输出简历格式
*  实例位置光盘/mr/6/6.2/6.2.2/03
首先建立求职者个人信息表,数据结构如表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组件实现
*    实例位置:光盘/mr/6/6.2/6.2.3/04
这里将连接数据库的代码封装到一个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组件实现
*    实例位置:光盘/mr/6/6.2/6.2.3/05
使用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()方法设置
         *    实例位置:光盘/mr/6/6.2/6.2.4/06
         首先从数据库中取出数据,利用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()构造函数来实现
*    实例位置:光盘/mr/6/6.2/6.2.4/07
不论是使用哪种方式实现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("&nbsp;","");
   }
}
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中引用标签文件。
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值