本文介绍一个数据库的自动生成报表例程。它是以数据源为数组的自动生成报表例程AutoReport.java作为基础。现在我们用数据库表做为数据源来实现自动生成报表打印功能。
15.6 自动生成报表例程
【例程15-11】数据源为数据库的自动生成报表例程AutoReport
在本书的前面“类和对象”章节我们已经介绍了,数据源为数组的自动生成报表例程AutoReport.java。现在我们用数据库表做为数据源来实现自动生成报表打印功能。
例程包含数据库环境创建的工具类和报表生成主类两部分:
- 数据库环境创建工具类DBUtils.java,代码如下:
package autoReport;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DBUtils {
/***嵌入式derby数据库的三行定义语句***/
static final String Driver = "org.apache.derby.jdbc.EmbeddedDriver";
static final String DbProtocol="jdbc:derby:"; //数据库协议
//变量DbName其中的";create=true"表示如果数据库不存在,就新建之。
static final String DbName = "D:\\\\StudentDB;create=true";
/***访问数据库的用户和密码***/
public static final String USER="root";
public static final String PWD="newsky26";
private static Connection con = null ;
/***创建学生基本信息表student的SQL语句脚本***/
public static final String CrtStudentTableSQL =
"CREATE TABLE student ( " +
"学号 CHAR(6) PRIMARY KEY," +
"姓名 VARCHAR(8) NOT NULL," +
"身高 REAL check(身高>=0)," +
"生日 DATE )" ;
/***创建课程成绩表courseScore的SQL语句脚本***/
private static final String CrtCourseScoreTableSQL =
"CREATE TABLE courseScore ( " +
"学号 CHAR(6) NOT NULL," +
"语文 INT DEFAULT 0 check(语文>=0 and 语文<=100), " +
"数学 INT DEFAULT 0 check(数学>=0 and 数学<=100), " +
"物理 INT DEFAULT 0 check(物理>=0 and 物理<=100), " +
"历史 INT DEFAULT 0 check(历史>=0 and 历史<=100), " +
"FOREIGN KEY (学号) REFERENCES student(学号) )" ;
/***查询学生基本信息表SQL语句脚本***/
private static final String QueryStudentSQL ="SELECT * FROM student";
/***测试(查询)视图View的SQL语句脚本***/
private static final String QueryViewSQL ="SELECT * FROM viewStu";
/***建立视图viewStu***/
private static final String CrtViewSQL="CREATE VIEW viewStu" +
" AS SELECT s.学号,姓名,语文,数学,物理,历史" +
" FROM student s,courseScore c WHERE s.学号=c.学号";
/***删除学生基本信息表student的SQL语句脚本***/
private static final String DropStudentTableSQL ="DROP TABLE student";
/***删除课程成绩表courseScore的SQL语句脚本***/
private static final String DropCourseScoreTableSQL ="DROP TABLE courseScore";
/***删除视图viewStu的SQL语句脚本***/
private static final String DropViewSQL ="DROP VIEW viewStu";
/***插入信息到学生基本信息表student的SQL语句脚本***/
public static final String InitStudentTableSQL =
"INSERT INTO student(学号, 姓名, 身高, 生日)"+
" VALUES ('200001','高玉宝',1.75,'2002-05-08'),"+
" ('200002','赵云',1.72,'2001-06-08'),"+
" ('200003','李云龙',1.76,'2000-07-18'),"+
" ('200004','钱江',1.70,'2001-10-08'),"+
" ('200005','欧阳明月',1.56,'2002-06-01')";
/***插入学生课程成绩表courseScore数据***/
private static final String InitCourseScoreTableSQL =
"INSERT INTO courseScore(学号,语文,数学,物理,历史) " +
" VALUES ('200001',95,80,65,70)," +
" ('200002',78,95,88,76)," +
" ('200003',82,60,75,85)," +
" ('200004',84,78,86,85)," +
" ('200005',82,72,87,75)";
/***查询并打印学生基本信息的方法***/
public static void Query学生(String sql) {
try {
if (con==null) //如果数据库连接不存在,建立数据库连接
con = connectDB();
Statement stmt = con.createStatement();
ResultSet rSet = stmt.executeQuery(sql);//执行查询
while(rSet.next()) {
String id = rSet.getString("学号");
String name = rSet.getString("姓名");
double h = rSet.getDouble("身高");
String hStr = String.format("%.2f", h);
Date date = rSet.getDate("生日");
String day = new SimpleDateFormat("YYYY-MM-dd").format(date);
System.out.println(id+'\\t'+name+'\\t'+hStr+'\\t'+day);
}
stmt.close();
}catch (SQLException se) {
se.printStackTrace();
}
}
/***查询并打印视图viewStu信息的方法。测试确认视图***/
public static void QueryView(String sql) {
try {
if (con==null) //如果数据库连接不存在,建立数据库连接
con = connectDB();
Statement stmt = con.createStatement();
ResultSet rSet = stmt.executeQuery(sql);//执行查询
while(rSet.next()) {
String id = rSet.getString("学号");
String name = rSet.getString("姓名");
int cScore = rSet.getInt("语文");
int pScore = rSet.getInt("物理");
System.out.println(id+'\\t'+name+'\\t'+cScore+'\\t'+pScore);
}
stmt.close();
}catch (SQLException se) {
se.printStackTrace();
}
}
//建立数据库连接,成功返回true,否则返回false。
public static Connection connectDB() {
String url = DbProtocol + DbName;
Connection con=null;
try { //加载驱动程序
Class.forName(Driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try { //连接数据库
con=DriverManager.getConnection(url, USER, PWD);
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
//根据参数,删除数据库表
public static void DropTable(String dropTableSQL)
{UpdateDB(dropTableSQL); }
//根据建表参数,新建数据库表
public static void CreateDbTable(String createTableSQL)
{UpdateDB( createTableSQL ); }
//根据sql参数、user和password,更新数据库表
public static void UpdateDB(String sql)
{
Connection conn = null;
conn = connectDB(); //联接数据库
try(Statement stmt = conn.createStatement();){
stmt.executeUpdate(sql); //执行更新
}catch (SQLException se) {
se.printStackTrace();
}
}
public static void main(String[] args) {
CreateDbTable(CrtStudentTableSQL); //创建学生表
UpdateDB(InitStudentTableSQL); //向学生表插入数据
Query学生(QueryStudentSQL); //查询、显示学生基本信息表所有记录
CreateDbTable(CrtCourseScoreTableSQL); //创建课程成绩表
UpdateDB(InitCourseScoreTableSQL); //向课程成绩表插入数据
CreateDbTable(CrtViewSQL); //创建视图
QueryView(QueryViewSQL); //从视图中查询
/***下面是删除表的代码,执行这些代码是为了反复演示建表时才需要***/
/*说明:由于CourseScore表依赖于学生表,删除时要先删CourseScore表*
DropTable(DropViewSQL); //删除视图
DropTable(DropCourseScoreTableSQL); //删除课程成绩表
DropTable(DropStudentTableSQL); //删除学生表
****/
}
} //数据库环境创建工具类DBUtils.java,代码结束。
数据库环境配置说明:main方法中有一部分代码注释了。如果放开注释代码,在程序运行结束时会自动清除程序所创建的数据库表和视图,这是为了反复进行演示建表测试。如果要为自动表格测试准备数据环境,则要恢复注释,然后再编译执行一次。
- 自动生成报表例程AutoReport.java
1,先来看数据源为数组的版本
【例程5-19】自动生成报表例程AutoReport,数据源为数组的版本
对于如下图的“学生成绩单”表格信息,如果用户有需求提取不同的字段列,只要传入报表参数和报表数据信息,本例程即可自动生成报表打印。
报表参数(class ReportParam)包括:字段名称、字段类型、各字段的打印长度、选定的打印字段表、每页的行数。另外再加报表的数据源(报表数据的信息)。程序可根据参数自动生成打印报表。
class ReportParam { //报表参数
String[] fieldsName; //所有字段名称
//字段类型:c表示文本型;n表示数值型
char[] fieldsType; //所有字段的类型
int[] fieldsLen; //所有字段的长度
int[] fieldsSelected; //选定出报表的字段
//每页行数:可按需重新设定
int rowsInPage = 28; //每页行数默认值是28
} //报表参数定义结束。
与C语言中“汉字是双字节,ASCII码字符是单字节”不同;Java语言用双字节表示一个字符,无论是汉字还是ASCII码字符,而且Java字符串的长度单位也是双字节的。然而,通常情况,打印机的ASCII码字符只占一个字节长度,汉字则占两个字节长度。打印机的处理方式,与C语言是兼容的,但与Java语言不兼容。因此Java程序比C语言需要额外多做一些调整工作。如下的方法用于“统计打印信息中的ASCII码字符个数”,在Java程序中是必需的:
public int getASCIICharCount(String str) {
if (str == null) { return 0; }
String reg = "[^\\\\x00-\\\\x7f]"; //非ASCII码字符的正则表达式
return str.replaceAll(reg, "").length();
}//方法结束。
说明:理解这段代码需要有正则表达式相关知识。调用replaceAll()方法后得到的是一个替换后的新字符串,而原始字符串不受影响。
自动打印报表例程的程序源代码:实例程序:自动打印报表AutoReport.java
package autoReport;
public class AutoReport {
private char[][] lineSign = {
{'┏','━','┳','┓'},{'┃',' ','┃','┃'},
{'┣','━','╋','┫'},{'┗','━','┻','┛'} };
/*** hLines[]数组共有4个StringBuilder(64),分别用于保存:
* hLines[0] 表格头第一行表格线 "┏━━┳━━┓";
* hLines[1] 表格头第二行字段名称行"┃名称┃名称┃";
* hLines[2] 表格中间分隔行表格线 "┣━━╋━━┫";
* hLines[3] 表格最后一行表格线 "┗━━┻━━┛"; ***/
private StringBuilder[] hLines={null,null,null,null};
public AutoReport(ReportParam param,String[][]report)
{
mkReportHead(param); //生成报表头
prnReport(param,report); //打印报表
}
/***生成报表头的方法***/
private void mkReportHead( ReportParam param ) {
int len,k,fieldsNum;
String fldName;
int[] fields = param.fieldsSelected;
String[] fieldNames = param.fieldsName;
//初始化hLines[]
for (int i = 0; i < hLines.length; i++)
hLines[i] = new StringBuilder(64);
/*** 处理表头、表尾及字段名行和中间分隔线行***/
fieldsNum = param.fieldsSelected.length;//选定的字段个数
for (int i = 0; i < fieldsNum; i++)
{//switch语句"0:"处理首字段;"1:"处理中间字段,"default:"处理末字段。
if (i==0) k = 0; //每行第一个字段特殊处理。
else if (i==fieldsNum–1) k = 99; //末字段,特殊处理。
else k = 1; //中间字段处理方法都一样。
len = param.fieldsLen[i]; //第i个字段的长度
//定制指定长度的表头字段名字符串
fldName= getFixedLenString(fieldNames[(fields[i]–1)],len,'c');
switch (k) {
case 0: //每行首字段的处理
//追加制表符的每行行首字符,'┏'、'┃'、'┣'、'┗'
hLines[0].append(lineSign[0][0]); //表格头第一行
hLines[1].append(lineSign[1][0]); //表格字段名行
hLines[2].append(lineSign[2][0]); //表格中间分隔行
hLines[3].append(lineSign[3][0]); //表格最后一行
/*** 以上是每行的第一个字段特殊字符处理。***/
/*** 以下是各字段相同处理部分***/
for (int l = 0; l < len; l++) { //循环len次
hLines[0].append(lineSign[0][1]); //表格头第一行
hLines[2].append(lineSign[2][1]); //表格中间分隔行
hLines[3].append(lineSign[3][1]); //表格最后一行
}
//表格字段名行 hLines[1]:设置字段名称。与上面三行处理不同
hLines[1].append(fldName);
break;
case 1: //每行的中间字段的处理
//追加制表符的每个字段间制表符,如'┳'、'┃'、'╋'、'┻'等
hLines[0].append(lineSign[0][2]); //表格头第一行
hLines[1].append(lineSign[1][2]); //表格字段名称行
hLines[2].append(lineSign[2][2]); //表格中间分隔行
hLines[3].append(lineSign[3][2]); //表格最后一行
/*** 以下是各字段相同处理部分***/
for (int l = 0; l < len; l++) { //循环len次
hLines[0].append(lineSign[0][1]); //表格头第一行
hLines[2].append(lineSign[2][1]); //表格中间分隔行
hLines[3].append(lineSign[3][1]); //表格最后一行
}
//表格字段名行 hLines[1]:设置字段名称。与上面三行处理不同
hLines[1].append(fldName);
break;
default: //每行的最后一个字段的处理
//追加制表符的每个字段间制表符,如'┳'、'┃'、'╋'、'┻'等
hLines[0].append(lineSign[0][2]); //表格头第一行
hLines[1].append(lineSign[1][2]); //表格字段名称行
hLines[2].append(lineSign[2][2]); //表格中间分隔行
hLines[3].append(lineSign[3][2]); //表格最后一行
/*** 以下是各字段相同处理部分***/
for (int l = 0; l < len; l++) { //循环len次
hLines[0].append(lineSign[0][1]); //表格头第一行
hLines[2].append(lineSign[2][1]); //表格中间分隔行
hLines[3].append(lineSign[3][1]); //表格最后一行
}
//表格字段名行 hLines[1]:设置字段名称。与上面三行处理不同
hLines[1].append(fldName);
/*** 以上的处理是各字段相同处理部分***/
/*** 下面的处理是每行最后一个字段特殊处理。***/
//追加表格的行结束字符:'┓'、'┃'、'┫'、'┛'
hLines[0].append(lineSign[0][3]); //表格头第一行
hLines[1].append(lineSign[1][3]); //表格字段名行
hLines[2].append(lineSign[2][3]); //表格中间分隔行
hLines[3].append(lineSign[3][3]); //表格最后一行
break;
}
}
}
/***打印报表的方法:数据源为二维字符串数组report,做了分页处理***/
private void prnReport(ReportParam param,String[][]report) {
int len,fieldsNum,rows;
char type;
String fixLenStr;
int PgRows = param.pageRows;//每页行数
int RptLen = report.length; //报表长度
for (int k=0,m=1;k < RptLen;m++) {
System.out.println(" 学 生 成 绩 单");
//打印表格表头
System.out.println(hLines[0].toString());
System.out.println(hLines[1].toString());
/***处理报表数据行***/
int[] fields = param.fieldsSelected;
fieldsNum = param.fieldsSelected.length;//选定的字段个数
StringBuilder dataLine = new StringBuilder(64);
//循环打印表格中间分隔行和数据行
rows = (PgRows<RptLen–k) ? PgRows:RptLen–k;
for (; rows>0; rows—,k++) {
/***表格数据行的生成***/
dataLine.delete(0, dataLine.capacity());//清空dataLine数据
for (int i = 0; i < fieldsNum; i++) { //每行中的各个字段的处理
dataLine.append(lineSign[1][0]); //插入表格字符
//定制指定长度的字符串
len = param.fieldsLen[i]; //第i个字段的长度
type = param.fieldsType[i];//第i个字段的类型
fixLenStr = getFixedLenString(report[k][fields[i]–1],len,type);
dataLine.append(fixLenStr);
}
dataLine.append(lineSign[1][3]); //插入行尾表格字符
System.out.println(hLines[2].toString()); //打印表格中间分隔行
System.out.println(dataLine.toString()); //打印数据行
}
//打印表格最后一行: 表格封底线行。
System.out.println(hLines[3].toString());
//打印页码
for (int i = 0; i < hLines[3].toString().length(); i++) {
System.out.print(' ');
}
System.out.println("第"+m+"页");
}
}
/*** 定制指定长度的字符串方法
* @param str 字段信息;
* @param len 字段列指定的打印长度;
* @param type 数据类型。c左对齐;n右对齐;
* @return 指定长度的字符串。
* ***/
public String getFixedLenString(String str,int len, char type)
{
if (str==null||str.length()==0) str="";
int num = getASCIICharCount(str);//单字节字符数
/***计算需要补空格的长度***/
int k = 2*(len–str.length())+num;//需要补空格的长度
StringBuilder sBuilder = new StringBuilder(64);
/***字符串小于指定打印长度的,用空格(' ')作为填充字符***/
if (type=='c') { //左对齐,右补空格
sBuilder.append(str);
for (int i = 0; i < k; i++) sBuilder.append(' ');
}
else { //右对齐,左补空格
for (int i = 0; i < k; i++) sBuilder.append(' ');
sBuilder.append(str);
}
return sBuilder.toString();
}
/*** 计算字符串中单字节字符出现的次数
* @param str
* @return
****/
public int getASCIICharCount(String str) {
if (str == null) { return 0; }
//非ASCII码字符的正则表达式
String reg = "[^\\\\x00-\\\\x7f]";
return str.replaceAll(reg, "").length();
}
public static void main(String[] args) {
char[] fieldsType = {'c','c','n','n','n','n','n','n'};
String[] fieldNames = {"姓名","学号","语文","数学","英语","物理","生物","历史"};
int[] fieldsLen = {4,2,2,2,2,2,2,2};
String[][] report = {
{"常昊","0801","88","100","68","85","80","65"},
{"刘国梁","0802","76","75","89","76","88","90"},
{"常遇春","0601","90","68","78","86","92","70"},
{"戚继光","0602","82","86","88","75","78","86"}};
//选定出报表的字段,用序号表示: 1 代表字段“姓名”,4代表字段“数学”,依次类推。
//选择前5个字段:"姓名","学号","语文","数学","英语"
int[] fields01 = {1,2,3,4,5};
int[] fields02 = {1,2,3,4,5,6,7,8}; //选择所有字段。
int[] fields03 = {1,2,3,4,6,8}; //随机选择字段。
//参数设定
ReportParam param = new ReportParam();
param.rowsInPage = 3; //重新设定每页行数
param.fieldsLen =fieldsLen;
param.fieldsType = fieldsType;
param.fieldsName = fieldNames;
param.fieldsSelected = fields01;
new AutoReport(param, report);
param.fieldsSelected = fields02;
new AutoReport(param, report);
param.fieldsSelected = fields03;
new AutoReport(param, report);
}
}
class ReportParam { //报表参数
String[] fieldsName; //所有字段名称
//字段类型:c表示文本型;n表示数值型
char[] fieldsType; //所有字段的类型
int[] fieldsLen; //所有字段的长度
int[] fieldsSelected;//选定出报表的字段
//每页行数:可按需重新设定
int rowsInPage = 28; //每页行数默认值是28
} /*** 实例程序:自动打印报表AutoReport.java 结束。***/
说明:Eclipse控制台(Consols)的打印效果不理想。请把控制台(Consols)的测试结果复制到文本编辑器(如UltraEdit)中查看。下面这个是程序测试结果的一部分:
2,把上面的自动制表例程进行一下改造,数据源使用数据库的版本
自动生成报表例程,只需在原来的自动生成报表例程AutoReport.java中新增一个构造器方法和一个prnDbReport()方法,其他的代码都可复用。新增的代码如下:
/***数据源为数据库的构造器方法***/
public AutoReport(ReportParam param)
{
mkReportHead(param); //生成报表头
prnDbReport(param); //打印数据源为数据库的报表
}
/***打印报表的方法:数据源为数据库结果集rSet,无标题,也未进行分页***/
private void prnDbReport(ReportParam param) {
int len,fieldsNum;
char type;
String fixLenStr,str;
//打印表格表头
System.out.println(hLines[0].toString());
System.out.println(hLines[1].toString());
/***处理报表数据行***/
fieldsNum = param.fieldsSelected.length;//选定的字段个数
StringBuilder dataLine = new StringBuilder(64);
//循环打印表格中间分隔行和数据行 数据源为数据库
String sql ="SELECT * FROM viewStu";
Connection con = DBUtils.connectDB(); //连接数据库
try {
Statement stmt = con.createStatement();
ResultSet rSet = stmt.executeQuery(sql);//执行查询
while(rSet.next()) {
System.out.println(hLines[2].toString()); //打印表格中间分隔行
dataLine.delete(0, dataLine.capacity()); //清空dataLine数据
for (int i = 0; i < fieldsNum; i++) { //每行中的各个字段的处理
dataLine.append(lineSign[1][0]); //插入表格字符
//定制指定长度的字符串
len = param.fieldsLen[i]; //第i个字段的长度
type = param.fieldsType[i];//第i个字段的类型
if (type=='c') { //'c'型
str = rSet.getString(i+1);
} else { //'n'型
str = ""+rSet.getInt(i+1);
}
fixLenStr = getFixedLenString(str,len,type);
dataLine.append(fixLenStr);
}
dataLine.append(lineSign[1][3]); //插入行尾表格字符
System.out.println(dataLine.toString());
}
//打印表格最后一行: 制表符表格封底线行。
System.out.println(hLines[3].toString());
} catch (SQLException se) {
se.printStackTrace();
}
}//prnDbReport()方法结束。
在AutoReport.java代码文件头部需增加以下导入语句:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import autoReport.DBUtils;
自动生成报表例程AutoReport.java中的main方法需要更新为:
public static void main(String[] args) {
/***数据源为数据源为数据库结果集rSet的测试方案***/
char[] fieldsType = {'c','c','n','n','n','n'};
String[] fieldNames = {"学号","姓名","语文","数学","物理","历史"};
int[] fieldsLen = {3,4,2,2,2,2};
int[] fields01 = {1,2,3,4};
int[] fields02 = {1,2,3,4,5,6};
//参数设定
ReportParam param = new ReportParam();
param.pageRows = 3; //重新设定每页行数
param.fieldsLen =fieldsLen;
param.fieldsType = fieldsType;
param.fieldsName = fieldNames;
param.fieldsSelected = fields01;
new AutoReport(param);
param.fieldsSelected = fields02;
new AutoReport(param);
}//main方法结束。
编译运行AutoReport,测试结果如下(控制台显示有点问题,这是复制到文本编辑器UltraEdit的显示效果):
如要增加表格头和分页效果,请参考数据源为二维字符串数组的方法。
评论前必须登录!
注册