Java数据库编程之【JDBC数据库例程】【ResultSet作为表格的数据源】【七】
16.3 用数据库结果集作为表格模型的数据源
本例程需要Java复杂组件 JTable表格 的相关知识。
说明:本节用数据库结果集作为表格模型的数据源其处理方式与DAO模式有显著区别。
【例程16-7】结果集表格模型例程DBsTblModelFrm
数据库结果集作为表格模型的数据源,用到了数据库编程中的可滚动可更新结果集,相关知识介绍请参考前文“数据库编程”章节的“可滚动可更新结果集例程”。
由于嵌入模式的Derby对“可滚动可更新结果集”的支持不友好。本例程我们使用网络模式的Derby数据库环境,网络模式Derby数据库服务的开启方法,请参考本书“数据库编程”中的“使用Derby网络模式数据库演示DbUtil”章节。另外,需要说明的是本例使用“JDBC的第二个创建数据库例程CreateDB”来创建数据库环境,由于CreateDB默认创建的是嵌入式的Derby数据库环境,因此我们要对CreateDB略作修改,使用网络模式的Derby数据库环境只需把二行定义语句修改成如下:
static final String driver = "org.apache.derby.jdbc.ClientDriver";static final String dbName = "//localhost:1527/StudentDB;create=true";
修改完成后,另存为CreateDB_network.java。其源代码如下:
package database;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
/***网络模式的derby数据库环境设置***/
public class CreateDB_network {static final String driver = "org.apache.derby.jdbc.ClientDriver";static final String dbName = "//localhost:1527/StudentDB;create=true";static final String dbProtocol="jdbc:derby:"; //数据库协议private static Connection con = null ;/***创建学生基本信息表student.student的SQL语句脚本***/private static final String CrtStudentTableSQL = "CREATE TABLE student.student ( " +"studentID CHAR(6) PRIMARY KEY," +"name VARCHAR(8) NOT NULL," +"height REAL check(height>=0)," +"mealCardAmt DECIMAL(10,2) DEFAULT 2000 check(mealCardAmt>=0)," +"totalScore INT DEFAULT 0 check(totalScore>=0)," +"birthDay DATE )" ;/***创建课程成绩表student.courseScore的SQL语句脚本***/private static final String CrtCourseScoreTableSQL = "CREATE TABLE student.courseScore ( " +"studentID CHAR(6) NOT NULL," +"courseName VARCHAR(8)," +"courseScore INT, " +"FOREIGN KEY (studentID) REFERENCES student.student(studentID) )" ;/***删除学生基本信息表student.student的SQL语句脚本***/private static final String DropStudentTableSQL ="DROP TABLE student.student" ; /***删除课程成绩表student.courseScore的SQL语句脚本***/private static final String DropScoreTableSQL ="DROP TABLE student.courseScore" ; /***插入学生基本信息表student.student数据***/private static final String InitStudentTableSQL ="INSERT INTO student.student(studentID, name, height,birthDay, mealCardAmt) "+ " VALUES ('200001','高玉宝',1.75,'2002-05-08',2000)," +" ('200002','赵云',1.72,'2001-06-08',2000),"+" ('200003','李云龙',1.76,'2000-07-18',2000),"+" ('200004','钱江',1.70,'2001-10-08',2000),"+" ('200005','刘胡兰',1.56,'2002-06-01',2000)"; /***插入学生课程成绩表student.courseScore数据***/private static final String InitCourseScoreTableSQL ="INSERT INTO student.courseScore(studentID, courseName, courseScore) " +" VALUES ('200001','语文',80),('200001','数学',75),('200001','英文',88)," +" ('200002','语文',70),('200002','数学',95),('200002','英文',68)," +" ('200003','语文',60),('200003','数学',100),('200003','英文',58)," +" ('200004','语文',85),('200004','数学',78),('200004','英文',85)," +" ('200005','语文',72),('200005','数学',85),('200005','英文',80)";//建立数据库连接public static Connection GetConnection( String dbName ) throws SQLException {if ( con != null && !con.isClosed() ) return con; //连接不为空,且没关闭.else if (connectDb(dbName)) { return con; } //尝试建立数据库连接else {System.err.println("数据库不存在!数据连接失败。 ");return con;}}//根据参数,删除数据库表public static void DropTable(String dropTableSQL){ UpdateDB(dropTableSQL); }//根据建表参数,新建数据库表public static void CreateDbTable(String createTableSQL){ UpdateDB( createTableSQL ); }//根据sql参数,更新数据库表public static void UpdateDB(String sql){String url = dbProtocol + dbName ;try {if (con==null) { //如果数据库连接不存在,建立数据库连接Class.forName(driver) ; /*加载数据库驱动*///建立数据库连接con = DriverManager.getConnection(url);}Statement stmt = con.createStatement();stmt.executeUpdate(sql) ;stmt.close();}catch (SQLException se) {PrintSQLException(se) ;}catch(ClassNotFoundException e){System.err.println("JDBC Driver:" + driver + " not in CLASSPATH") ;}}//建立数据库连接,成功返回true,否则返回false。public static boolean connectDb(String dbName) {String url = dbProtocol + dbName;boolean rnflg = false ;try{Class.forName(driver) ; //加载驱动程序con = DriverManager.getConnection( url ); //联接数据库SQLWarning swarn = con.getWarnings(); if(swarn != null) PrintSQLWarning(swarn);rnflg = true ;} catch (SQLException se) {PrintSQLException(se) ;}catch(ClassNotFoundException e){System.err.println("JDBC Driver " + driver + " not found in CLASSPATH") ;}return(rnflg) ;}/***打印SQLException异常出错信息的方法***/public static void PrintSQLException(SQLException se) {while(se != null) {System.out.print("SQLException: State: " + se.getSQLState());System.out.println(" Severity: " + se.getErrorCode());System.out.println(se.getMessage()); se = se.getNextException();}} /***打印SQLWarning警告信息的方法***/public static void PrintSQLWarning(SQLWarning sw) {while(sw != null) {System.out.print("SQLWarning: State=" + sw.getSQLState()) ;System.out.println(" Severity = " + sw.getErrorCode()) ;System.out.println(sw.getMessage()); sw = sw.getNextWarning();}}public static void main(String[] args) {System.out.print("开始创数据库!\n");CreateDbTable(CrtStudentTableSQL); //创建“学生基本信息表”CreateDbTable(CrtCourseScoreTableSQL); //创建“课程成绩表”UpdateDB(InitStudentTableSQL); //插入学生基本信息表数据UpdateDB(InitCourseScoreTableSQL); //插入学生课程成绩表数据System.out.print("数据库Student创建成功!");}
} //创建数据库CreateDB_network.java源码结束。
创建数据库环境步骤:
1,先开启Derby数据库服务;
2,再编译并执行CreateDB_network.java,就可建立网络模式的Derby数据库环境。
类似地,CreateDB_network.java略作修改后也可适用于MySQL数据库环境。有兴趣的读者可自行修改实现。
下面例程演示如何从数据库的结果集中获取表格信息,共包括两个源码文件:
一个是自定义的数据库查询结果集表格模型类aResultSetModel.java;
另一个是结果集表格模型演示例程DBsTblModelFrm.java:
自定义数据库查询结果集表格模型aResultSetModel.java开始:
package table;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import javax.swing.table.AbstractTableModel;
/*把数据库ResultSet包装成TableModel*/
public class aResultSetModel extends AbstractTableModel {private final String[][] colNames = { {"学 号","课程名","成 绩"},{"学 号","姓 名","身高","饭卡余额","总 成 绩","出生日期"} };private String tableName;private ResultSet rSet;private ResultSetMetaData rSetMetaData;public aResultSetModel(ResultSet rsltSet,String table) {tableName = table;rSet = rsltSet;try {rSetMetaData = rSet.getMetaData();} catch (Exception e) { e.printStackTrace(); }}@Overridepublic int getColumnCount() {try {return rSetMetaData.getColumnCount();} catch (Exception e) {e.printStackTrace();return 0;}}@Overridepublic int getRowCount() {try {rSet.last(); //定位到结果集的最后一条记录return rSet.getRow();} catch (Exception e) {e.printStackTrace();return 0;}}@Overridepublic Object getValueAt(int row, int col ) {try {rSet.absolute(row+1); //将结果集定位到指定的记录//System.out.println(rSet.getObject(col+1));return rSet.getObject(col+1);} catch (Exception e) {e.printStackTrace();return null;}}@Override /**设置单元格可编辑状态**/public boolean isCellEditable(int row, int col) { return true; }@Override /**当用户编辑单元格时,将触发此方法**/public void setValueAt(Object value, int row, int col) {try {rSet.absolute(row+1); //将结果集定位到指定的记录rSet.updateObject(col+1, value); //修改单元格的值rSet.updateRow(); //提交结果集中行的更新至数据库中fireTableCellUpdated(row, col); //触发单元格的修改事件} catch (Exception e) {e.printStackTrace();}}@Overridepublic String getColumnName(int col) {int index = tableName.equalsIgnoreCase("student") ? 1 : 0; return colNames[index][col];}
} //自定义数据库查询结果集表格模型aResultSetModel.java结束。
程序说明: aResultSetModel类,继承自AbstractTableModel类,利用数据库查询结果集ResultSet重写了getColumnCount()、getRowCount()和getValueAt(int row, int col )三个方法,用表格来展示ResultSet结果集里的数据记录。除此之外,该表格模型类还重写了getColumnName(int col)用于定制表格头;重写方法isCellEditable(int row, int col)使表格单元格可编辑,本例设置所有的单元格为可编辑,在实际应用中完全可以实现按需求设定某些列可编辑;重写方法setValueAt(Object value, int row, int col)实现用户编辑单元格后,将修改同步到数据库结果集中的功能。
简单的数据库查询结果集表格模型类的演示程序及测试页面,版本一:
结果集表格模型演示例程DBsTblModelFrm.java开始:
package table;
/***用数据库结果集作为表格数据源,版本一***/
import static java.awt.BorderLayout.*;
import static java.sql.ResultSet.*;
import java.awt.Dimension;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.sql.*;
import javax.swing.*;import database.CreateDB_network; //需要导入这个类
public class DBsTblModelFrm extends JFrame {
private static final String dbName = "//localhost:1527/StudentDB"; //网络模式Derby数据库private static final String querySQL = "select * from student.student";private JScrollPane scrollPane = null;private JScrollPane scrlPane = null;private JTextArea changeMsg = new JTextArea(4, 80);private TableModel model;private ResultSet resultSet;private Connection con;private Statement stmt;public DBsTblModelFrm() throws SQLException {setTitle("简单数据库表数据表格演示");scrlPane = new JScrollPane(changeMsg);add(scrlPane, SOUTH);con = CreateDB.GetConnection(dbName); //连接数据库//创建Statementstmt = con.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_UPDATABLE);//查询用户选择的表内容resultSet = stmt.executeQuery(querySQL); //执行查询/**使用查询到的结果集创建TableModel实例**/model = new aResultSetModel(resultSet,"student"); //表名studentmodel.addTableModelListener(e->{ //注册表格模型TableModel监听器int rowIdx = e.getFirstRow();int colIdx = e.getColumn();Object v = model.getValueAt(rowIdx, colIdx);changeMsg.append("行索引:"+rowIdx+" 列索引:"+colIdx+" 新值:"+v+"\n");});/**使用TableModel创建表格JTable,并添加到主窗体**/JTable table = new JTable(model);table.setAutoCreateRowSorter(true); //设置点击表格列头可自动排序table.setFillsViewportHeight(true); //设置表格自动填充外围视图/*** 隐藏(不显示)第4列"饭卡余额" ***/TableColumnModel colModel = table.getColumnModel();colModel.getColumn(3).setMinWidth(0); //设置第4列的最小列宽为0colModel.getColumn(3).setMaxWidth(0); //设置第4列的最大列宽为0scrollPane = new JScrollPane(table);add(scrollPane, CENTER);scrlPane = new JScrollPane(changeMsg);add(scrlPane, SOUTH);validate();setVisible(true);pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //退出时关闭程序}@Overridepublic Dimension getPreferredSize() { return new Dimension(400, 260); }public static void main(String[] args) throws SQLException { new DBsTblModelFrm(); }
} //结果集表格模型演示例程DBsTblModelFrm.java结束。
说明: 运行此例程需要在程序中导入创建数据库程序CreateDB_network.java。首先用CreateDB_network.java创建初始数据库环境后,再执行本例程。双击表格单元,假如“身高”数据,可修改数据。首次执行时修改一些数据,退出程序;第二次再次执行时从数据库中读到的数据应当已是修改后的数据。
程序运行效果图如下:
下面对上面的例程做一下改进,使用的是同一个数据库,同一个结果集表格模型,我们可以在组合框中选择数据库表,从而显示不同的界面,版本二:
结果集表格模型演示例程,版本二TModelDbFrm.java开始:
package table;
/***用数据库结果集作为表格数据源,版本二***/
import static java.awt.BorderLayout.*;
import static java.sql.ResultSet.*;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.*;
import javax.swing.*;
import javax.swing.table.TableModel;import database.CreateDB_network; //导入database包中的CreateDB_network类
public class TModelDbFrm extends JFrame {
private static final String dbName = "//localhost:1527/StudentDB"; private static final String schemaName ="student";private JScrollPane scrollPane = null;//private JScrollPane scrlPane = null;private JComboBox<String> tableNameBox = new JComboBox<>();private JTextArea changeMsg = new JTextArea(4, 80);private TableModel model;private ResultSet resultSet;private Connection con;private Statement stmt;//下面定义了一个内部类,列表框的动作侦听器类class ComBoxActLsn implements ActionListener {@Overridepublic void actionPerformed(ActionEvent event) {try {//如果JScrollPane存在,删除原有的表格if (scrollPane!=null) TModelDbFrm.this.remove(scrollPane);//从组合框中得到表名String tableName = (String)tableNameBox.getSelectedItem();//如果结果集不为空,则关闭之if(resultSet!=null)resultSet.close();String querySQL = "select * from "+ schemaName+"." +tableName;//查询用户选择的表内容resultSet = stmt.executeQuery(querySQL); //执行查询/**使用查询到的结果集创建TableModel实例**/model = new aResultSetModel(resultSet,tableName);//注册表格模型TableModel监听器model.addTableModelListener(e->{int rowIdx = e.getFirstRow();int colIdx = e.getColumn();Object v = model.getValueAt(rowIdx, colIdx);changeMsg.append("行索引:"+rowIdx+" 列索引:"+colIdx+" 新值:"+v+"\n");});//用TableModel创建表格JTable,同时把表格添加到主窗体中JTable table = new JTable(model);scrollPane = new JScrollPane(table);TModelDbFrm.this.add(scrollPane,CENTER);TModelDbFrm.this.validate();TModelDbFrm.this.repaint();} catch (SQLException e) { e.printStackTrace(); }}}public TModelDbFrm() throws SQLException {setTitle("用数据库表数据演示表格");JPanel panel = new JPanel();ComBoxActLsn listener = new ComBoxActLsn();tableNameBox.addActionListener(listener); //注册组合框监听器setComBoxData(); //连接数据库,设置组合框中的表名panel.add(new JLabel("请选择数据库表名:"));panel.add(tableNameBox);add(panel,NORTH);add(new JScrollPane(changeMsg), SOUTH);/**使用TableModel创建表格JTable,并添加到主窗体**/JTable table = new JTable(model);scrollPane = new JScrollPane(table);add(scrollPane, CENTER);validate();setVisible(true);pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //退出时关闭程序}/**从数据库中获取表名,添加到组合框中**/public void setComBoxData() throws SQLException {con = CreateDB.GetConnection(dbName); //连接数据库DatabaseMetaData metaData = con.getMetaData();//创建Statementstmt = con.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_UPDATABLE);ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"});//将表名添加到JComboBox中while (tables.next() ) tableNameBox.addItem(tables.getString(3));tables.close();}@Overridepublic Dimension getPreferredSize() { return new Dimension(400, 260); }public static void main(String[] args) throws Exception { new TModelDbFrm(); }
} //结果集表格模型演示例程,版本二TModelDbFrm.java结束。
说明: 版本二和版本一两个例程不可同时在同一电脑中演示,因为版本一运行时会对STUDENT表格加锁。若此时版本二例程中选择STUDENT表格,例程就会处于等待状态,而没有响应。
下图是版本二程序运行页面之一(在组合框中选择另一个表名,将显示另一个页面):