当前位置: 首页 > java >正文

Java数据库编程之【JDBC数据库例程】【自动生成报表】【六】

Java数据库编程【自动生成报表】【六】

  • 15.6 自动生成报表例程

本文介绍一个数据库的自动生成报表例程。它是以数据源为数组的自动生成报表例程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的显示效果):
在这里插入图片描述

如要增加表格头和分页效果,请参考数据源为二维字符串数组的方法。

http://www.xdnf.cn/news/17685.html

相关文章:

  • Gradient Descent for Logistic Regression|逻辑回归梯度下降
  • Qwen-OCR:开源OCR技术的演进与全面分析
  • 【数据结构】——顺序表链表(超详细解析!!!)
  • Flink运行时的实现细节
  • COAT: 压缩优化器状态和激活以实现内存高效的FP8训练
  • apache+虚拟主机
  • @(AJAX)
  • 使用Spring Boot对接欧州OCPP1.6充电桩:解决WebSocket连接自动断开问题
  • 日志管理--g3log
  • 前端项目一键换肤
  • IEEE 2025 | 重磅开源!SLAM框架用“法向量+LRU缓存”,将三维重建效率飙升72%!
  • 单例模式,动态代理,微服务原理
  • 操作系统1.6:虚拟机
  • 从原理到实践:一文掌握Kafka的消息生产与消费
  • 【bug 解决】串口输出字符乱码的问题
  • pdftk - macOS 上安装使用
  • 干货分享|如何从0到1掌握R语言数据分析
  • OpenAI传来捷报,刚刚夺金IOI,实现通用推理模型的跨越式突破
  • 如何实现PostgreSQL的高可用性,包括主流的复制方案、负载均衡方法以及故障转移流程?
  • 【接口自动化】-11-接口加密签名 全局设置封装
  • 容器安全扫描工具在海外云服务器环境的集成方法
  • Element用法---Loading 加载
  • npm、pnpm、yarn区别
  • 一周学会Matplotlib3 Python 数据可视化-绘制饼状图(Pie)
  • 前沿技术借鉴研讨-2025.8.12 (数据不平衡问题)
  • Web项目Excel文件处理:前端 vs. 后端,企业级如何选择?
  • 【3】Transformers快速入门:大语言模型LLM是啥?
  • 11-docker单机版的容器编排工具docker-compose基本使用
  • centos 7 如何安装 ZipArchive 扩展
  • MySQL 数据库表操作与查询实战案例