Java 高级技术 课程 Java 语言的高级特性 李戈 北京大学信息科学技术学院软件研究所 2009 年 4 月 18 日
两层客户机 / 服务器体系结构
三层客户机 / 应用服务器 / 服务器体系结构
JDBC JDBC (Java Database Connection) 实现 Java 应用程序与数据库之间的接口功能 JDBC 建立在 SQL 的基础上, 应用程序可嵌入 SQL 访问和操作数据库 ; JDBC 为应用程序提供一些编程接口, 定义在 java.sql 程序包以及 JDK 1.2 版以后的 javax.sql 程序包中 ; JDBC 与数据库管理系统之间则通过安装与特定数据库管理系统相关的驱动程序进行通信
JDBC 的结构
JDBC 的结构 JDBC 驱动器管理器 (Driver Manager) 管理各种不同的 JDBC 驱动程序, 它们 或者使用专用的数据库访问协议 (Proprietary database access protocol) 实现数据库的访问 ; 或者使用 JDBC 中间件协议 (JDBC Middleware Protocol) 与另外的 JDBC 网络驱动器 (JDBC-Net Driver) 相连 ; JDBC-ODBC Bridge Driver(JDBC-ODBC 桥接驱动器 ) 通过 ODBC(Open Database Connection) 以及数据库驱动器与数据库相连 ODBC 提供的是 C 语言编程接口 JDBC-ODBC 桥接驱动器负责将 ODBC 提供的 C 语言编程接口转换为 Java 语言编程接口
JDBC 使用三部曲 利用 JDBC 访问数据库 (1) 首先向 JDBC 驱动器管理器注册所使用的数据库驱动程序 ; (2) 通过 JDBC 驱动器管理器获得一个数据库连接 (Database connection); (3) 向该连接发送 SQL 语句, 获得 SQL 语句的执行结果, 完成对数据库的访问 ;
Step1: 向 JDBC 驱动器管理器注册所使用的数据库驱动程序 通过要求 JVM 装载数据库驱动程序所对应的类而实现 例如 JDBC-ODBC 桥接驱动器是 Java 应用程序常用的数据库驱动 程序, 类名为 sun.jdbc.odbc.jdbcodbcdriver, 以下语句可完 成向 JDBC 驱动器管理器注册该驱动程序的功能 : Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); 类 Class 提供的 forname() 方法根据类名查找相应的类, 这种 查找会使得 JVM 装载这个类 通常数据库驱动程序的类有一段静态初始化代码, 这段代 码在 JVM 装载这个类时启动, 负责创建驱动器的一个实例, 并将该实例向 JDBC 驱动器管理器注册
Step2: 获得数据库连接 Java 应用程序通过 JDBC 的驱动器管理器工具类 DriverManager 提供的静态方法 getconnection() 建立数据库连接 : static Connection getconnection(string datasourceurl); static Connection getconnection(string datasourceurl, String user, String password); 参数 user 和 password 是用户名和相应的密码 ; 参数 datasourceurl 是数据源的资源定位名 ; 使用 JDBC-ODBC 桥接驱动器时, 数据源的资源定位名具有如下格式 : jdbc:odbc:phonebook 其中 jdbc:odbc 是这种驱动程序所固定使用的串,PhoneBook 则是在配置 ODBC 数据源时所指定的数据源名称 获得与数据源 PhoneBook 的连接 : Connection connection = DriverManager.getConnection("jdbc:odbc:PhoneBook");
Step3: 使用 SQL 访问数据库 先创建 SQL 语句对象然后发送和执行 SQL 语句 通常使用接口 Connection 的以下方法创建 SQL 语句对象 : Statement createstatement(); PreparedStatement PreparedStatement(String sql); CallableStatement preparecall(string sql); 接口 CallableStatement 是接口 PreparedStatement 的子接口, 而接口 PreparedStatement 又是接口 Statement 的子接口
Statement 类型的对象 Statement 类型的对象可发送和执行没有参数的 SQL 语句, 这通过接口 Statement 的下述方法实现 : ResultSet executequery(string sql); int executeupdate(string sql); 根据不同的 SQL 语句调用不同的方法, 当执行的是 SQL 查询语句时调用方法 executequery() 当执行的是 SQL 数据更新语句 ( 包括 SQL 插入语句 修改语句和删除语句 ) 调用方法 executeupdate()
PreparedStatement 类型的对象 PreparedStatement 类型的对象可发送和执行带参数的 SQL 语句 创建类型为接口 PreparedStatement 的对象时应预先指定含有参数的 SQL 语句, 参数使用? 占位表示 ; 在发送和执行 SQL 语句前, 必须先使用各种 SetXxx() 方法设置 SQL 语句参数应取的值 ; 然后再调用以下方法发送和执行预先指定的 SQL 语句 : ResultSet executequery(); int executeupdate(); CallableStatement 类型的对象可发送和执行数据库的存储过程 CallableStatement 是接口 PreparedStatement 的子接口
SQL 语句对象的使用 创建一个类型为 PreparedStatement 的对象, 使用 '?' 占据要设置值的参数的位置 : String sql = "SELECT * FROM Person WHERE id =? AND birthday <=?"; PreparedStatement stm = connection.preparestatement(sql); 设置? 占位符的值 : stm.setstring(1, new String("leeman")); stm.setdate(2, new Date(1970, 2, 2)); 方法 SetXxx() 的第一个参数指定参数的位置, 即第几个? 所对应的参数, 第二个参数表明要设置的值 ; 注意 : 参数的类型决定了所调用方法 SetXxx() 的名称 表 Person 的字段 id 应为字符串, 则调用方法 setstring() 字段 birthday 应为日期值, 则调用方法 setdate()
关于数据类型 数据库使用中的数据类型问题 : Java 语言的数据类型与 SQL 所支持的数据类型不完全相同 ; SQL 所支持的数据类型与数据库中数据表的字段类型不完全相同 ; 要建立 Java 语言的数据类型与数据表的字段类型之间的映射 : 首先完成 Java 语言的数据类型与 JDBC 支持的 SQL 数据类型之间进行映射 ; JDBC 的数据库驱动程序进一步负责 SQL 数据类型与实际的数据表的字段类型之间进行映射
Java 数据类型与 SQL 数据类型之间的映射
JDBC 支持的 SQL 数据类型与 Access 数据类型之间的映射
返回结果集 ResultSet 类型为 ResultSet 的对象存放了执行 SQL 查询语句所返回的结果 执行 SQL 查询语句后返回包含结果集的 ResultSet 对象 ; 执行 SQL 更新语句后返回 数据表受影响的数据记录数 ; 接口 ResultSet 支持迭代器访问模式 : ResultSet result = stm.executequery(); while (result.next()) { } String id = result.getstring("id"); String name = result.getstring("name"); boolean sex = result.getboolean("sex"); Date birthday = result.getdate("birthday"); ResultSet 具有的字段由所执行的 SQL 语句目标字段列表确定 第一次调用 next() 方法会将查询结果集的游标移动到第一条数据记录 System.out.println(id + "\t" + name + "\t" + sex + "\t" + birthday);
JDBC 对象的关闭 及时关闭对象是良好的编程习惯 结束数据访问后, 应关闭查询结果集 ; 发送和执行完所有的 SQL 语句后, 应关闭 SQL 语句对象 ; 当不再准备访问数据库后就应关闭数据库连接 ; 接口 ResultSet Statement PreparedStatement Connection 都提供了 close() 方法分别释放查询结果集 SQL 语句对象 数据库连接所占用的资源并将它们关闭 PreparedStatement 类型的对象在发送和执行完 SQL 语句后, 可使用该接口提供的 clearparameters() 清除所设置的参数值
步骤 : 使用 JDBC 访问数据库步骤 1. 向驱动器管理器注册数据库驱动程序 ; 2. 通过驱动器管理器类获取与要访问的数据源之间的数据库连接 ; 3. 创建发送和执行 SQL 语句的 SQL 语句对象 ; 4. 必要时设置 SQL 语句的参数值 ; 5. 发送和执行 SQL 语句 ; 6. 当执行的是 SQL 查询语句时, 获得查询结果集, 运用迭代器模式访问查询结果集 ; 7. 关闭与数据库访问有关的对象, 释放其占用的资源
相关对象 使用 JDBC 访问数据库
import java.sql.*; 使用 JDBC 访问数据库示例 public class DemoJDBC { public static void main(string[] args) { String driver = "sun.jdbc.odbc.jdbcodbcdriver"; String source = "jdbc:odbc:phonebook"; try { // 查找用于 JDBC 驱动的类, 查找 会使得 JVM 装入该类 // 该类的静态初始化语句块会对驱动程序进行初始化 Class.forName(driver); } catch (ClassNotFoundException exc) { // 若没有驱动程序, 应用程序无法继续运行, 退出程序 System.out.println(" 没有发现驱动程序 :" + driver); exc.printstacktrace(); System.exit(1); }
try { // 建立与指定数据库的连接 Connection connection = DriverManager.getConnection(source); // 如果连接成功则检测是否有警告信息 SQLWarning warn = connection.getwarnings(); while (warn!= null) { System.out.println(warn.getMessage()); warn = warn.getnextwarning(); } // 创建一个用于执行预编译 SQL 的语句对象 String sql = "SELECT * FROM Person WHERE id =? AND birthday <=?"; PreparedStatement pstm = connection.preparestatement(sql); // 设置预编译 SQL 语句的参数值 pstm.setstring(1, new String("leeman")); pstm.setdate(2, new Date(1970, 2, 2)); // 发送和执行预编译的 SQL 语句, 获得查询结果集 ResultSet result = pstm.executequery();
// 使用迭代模式访问查询结果集 while (result.next()) { String id = result.getstring("id"); String name = result.getstring("name"); String sex = result.getboolean("sex")? " 女 " : " 男 "; Date birthday = result.getdate("birthday"); System.out.println(id + "\t" + name + "\t" + sex + "\t" + birthday); } // 关闭查询结果集 result.close(); // 关闭 SQL 预编译语句 pstm.close();
}}} // 创建执行简单 SQL 语句的 SQL 语句对象 Statement stm = connection.createstatement(); // 发送和执行简单 SQL 语句, 获取查询结果集 sql = "SELECT * FROM Person"; result = stm.executequery(sql); // 使用迭代模式访问查询结果集 while (result.next()) { String id = result.getstring("id"); String name = result.getstring("name"); String sex = result.getboolean("sex")? " 女 " : " 男 "; Date birthday = result.getdate("birthday"); System.out.println(id + "\t" + name + "\t" + sex + "\t" + birthday); } // 关闭查询结果集 result.close(); // 关闭 SQL 语句 stm.close(); // 关闭数据库连接 connection.close(); } catch (SQLException exc) { System.out.println(" 在执行数据库访问时发生了错误!"); exc.printstacktrace();
元数据 关于数据的数据 (Metadata) 数据表元信息 检索结果元数据的访问 与数据表的字段有关的信息, 如字段名称 字段数据类型 字段长度等 利用类型为接口 ResultSetMetaData 的对象可以查询结果集的元信息 ResultSetMetaData 类型的对象 由接口 ResultSet 的方法 getmetadata() 返回 提供了一系列方法访问元数据 : int getcolumncount(); String getcolumnname(int index); String getcolumntypename(int index);
import java.sql.*; public class DemoMetaData { public static void main(string[] args) { String driver = "sun.jdbc.odbc.jdbcodbcdriver"; String source = "jdbc:odbc:phonebook"; try { Class.forName(driver); } catch (ClassNotFoundException exc) { System.out.println(" 没有发现驱动程序 :" + driver); exc.printstacktrace(); System.exit(1); } try { // 建立与指定数据库的连接 打印出数据表 Person 的数据字段的最重要信息, 包括字段名称 长度 类型及所属的 Java 类 Connection connection = DriverManager.getConnection(source); // 如果连接成功则检测是否有警告信息 SQLWarning warn = connection.getwarnings(); while (warn!= null) { System.out.println(warn.getMessage()); warn = warn.getnextwarning(); }
}}} Statement stm = connection.createstatement(); String sql = "SELECT * FROM Person"; ResultSet result = stm.executequery(sql); ResultSetMetaData metadata = result.getmetadata(); int columncount = metadata.getcolumncount(); System.out.println(" 字段名称 \t 字段类型 \t 字段长度 \t 字段所属类 "); for (int i = 0; i < columncount; i++) { // 注意字段下标从 1 开始计算 System.out.println(metaData.getColumnName(i + 1) + "\t" + metadata.getcolumntypename(i + 1) + "\t" + metadata.getprecision(i + 1) + "\t" + metadata.getcolumnclassname(i + 1)); } result.close(); stm.close(); connection.close(); } catch (SQLException exc) { System.out.println(" 在执行数据库访问时发生了错误!"); exc.printstacktrace();
将与数据库访问有关的操作封装在类中 import java.sql.*; import java.io.*; public class DatabaseAccess { // 定义一个类封装数据库操作 ; protected final String driver = "sun.jdbc.odbc.jdbcodbcdriver"; protected final String source = "jdbc:odbc:phonebook"; protected Connection connection; protected Statement statement; public DatabaseAccess() throws SQLException { try { Class.forName(driver); } catch (ClassNotFoundException exc) { } connection = DriverManager.getConnection(source); SQLWarning warn = connection.getwarnings(); while (warn!= null) { System.out.println(warn.getMessage()); warn = warn.getnextwarning();} statement = connection.createstatement(); } public void close() throws SQLException {...} public ResultSet query(string sql) throws SQLException{...} public void update(string sql) throws SQLException {...} public ResultSet preparedquery(string sql) throws SQLException{...} public void preparedupdate() throws SQLException{...} public void setparameter(int index, boolean value) throws SQLException {...}...
import java.sql.*; public class DemoDatabaseAccess { public static void main(string args[]) { try { String sql = "DELETE FROM Person WHERE id =?"; ResultSet data = null; DatabaseAccess da = new DatabaseAccess(); da.prepare(sql); da.setparameter(1, "clint"); da.preparedupdate(); sql = "SELECT * FROM Person"; data = da.query(sql); displaydata(data); da.close(); } catch (SQLException exc) { exc.printstacktrace(); System.exit(1); 利用封装类 DatabaseAccess 进行数据库操作 ; }} private static void displaydata(resultset data) throws SQLException { System.out.println(" 代码 \t 姓名 \t 出生日期 \t 性别 "); while (data.next()) { String id = data.getstring("id"); String name = data.getstring("name"); Date birthday = data.getdate("birthday"); String sex = data.getboolean("sex")? " 女 " : " 男 "; System.out.println(id + "\t" + name + "\t" + birthday + "\t" + sex); }}}
Projct1: 通信信息管理工具 利用 Java 设计并实现一个个人通信信息管理程序, 该程序必须 ( 但不限于 ) 完成以下基本功能 : (1) 提供良好的 GUI 以支持个人通信信息的存储功能 ; (2) 支持通信信息的增加 删除 修改功能 ; (3) 支持通信信息的 按字段检索 和 模糊检索 功能 ; (4) 支持通信信息的排序浏览功能 ; 非功能需求 : 要求系统具有良好的可扩展性 易于对个人通信信息所包含的数据项进行更改 ; 系统结构具有良好的层次性, 易于扩展
Project 开发要求 Projct1: 通信信息管理工具 自行分组 2 人一组, 每个人最多属于 2 个组 ; 文档要求 必须提交分析 设计文档 ; 文档格式可参考 GB856T 88 标准 项目期限 5 月 10
好好想想, 有没有问题? 谢谢!