|
常用链接
留言簿(1)
随笔档案
文章分类
文章档案
搜索
最新评论
Powered by: 博客园
模板提供:沪江博客
|
|
|
|
|
发新文章 |
|
|
|
■DriverManager 类 DriverManager 类是 JDBC 的管理层,作用于用户和驱动程序之间。它跟踪可用的驱动程序,并在数据库和相应驱动程序之间建立连接。另外,DriverManager
类也处理诸如驱动程序登录时间限制及登录和跟踪消息的显示等事务。
对于简单的应用程序,一般程序员需要在此类中直接使用的唯一方法是
DriverManager.getConnection。正如名称所示,该方法将建立与数据库的连接。JDBC 允许用户调用
DriverManager 的方法 getDriver、getDrivers 和 registerDriver 及 Driver 的方法
connect。但多数情况下,让 DriverManager 类管理建立连接的细节为上策。 ■跟踪可用驱动程序
DriverManager 类包含一列 Driver 类,它们已通过调用方法 DriverManager.registerDriver
对自己进行了注册。所有 Driver 类都必须包含有一个静态部分。它创建该类的实例,然后在加载该实例时 DriverManager
类进行注册。这样,用户正常情况下将不会直接调用
DriverManager.registerDriver;而是在加载驱动程序时由驱动程序自动调用。加载 Driver 类,然后自动在
DriverManager 中注册的方式有两种: 通过调用方法 Class.forName。这将显式地加载驱动程序类。由于这与外部设置无关,因此推荐使用这种加载驱动程序的方法。以下代码加载类 acme.db.Driver: Class.forName("acme.db.Driver");
如果将 acme.db.Driver 编写为加载时创建实例,并调用以该实例为参数的 DriverManager.registerDriver(本该如此),则它在 DriverManager 的驱动程序列表中,并可用于创建连接。
通过将驱动程序添加到 java.lang.System 的属性 jdbc.drivers 中。这是一个由 DriverManager
类加载的驱动程序类名的列表,由冒号分隔:初始化 DriverManager 类时,它搜索系统属性
jdbc.drivers,如果用户已输入了一个或多个驱动程序,则 DriverManager 类将试图加载它们。以下代码说明程序员如何在
~/.hotjava/properties 中输入三个驱动程序类(启动时,HotJava 将把它加载到系统属性列表中): jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver; 对 DriverManager 方法的第一次调用将自动加载这些驱动程序类。
注意:加载驱动程序的第二种方法需要持久的预设环境。如果对这一点不能保证,则调用方法 Class.forName
显式地加载每个驱动程序就显得更为安全。这也是引入特定驱动程序的方法,因为一旦 DriverManager 类被初始化,它将不再检查
jdbc.drivers 属性列表。 在以上两种情况中,新加载的 Driver
类都要通过调用 DriverManager.registerDriver 类进行自我注册。如上所述,加载类时将自动执行这一过程。 由于安全方面的原因,JDBC 管理层将跟踪哪个类加载器提供哪个驱动程序。这样,当 DriverManager 类打开连接时,它仅使用本地文件系统或与发出连接请求的代码相同的类加载器提供的驱动程序。 ■建立连接
加载 Driver 类并在 DriverManager 类中注册后,它们即可用来与数据库建立连接。当调用
DriverManager.getConnection 方法发出连接请求时,DriverManager
将检查每个驱动程序,查看它是否可以建立连接。 有时可能有多个 JDBC 驱动程序可以与给定的 URL
连接。例如,与给定远程数据库连接时,可以使用 JDBC-ODBC 桥驱动程序、JDBC
到通用网络协议驱动程序或数据库厂商提供的驱动程序。在这种情况下,测试驱动程序的顺序至关重要,因为 DriverManager
将使用它所找到的第一个可以成功连接到给定 URL 的驱动程序。 DriverManager
试图按注册的顺序使用每个驱动程序(jdbc.drivers
中列出的驱动程序总是先注册)。它将跳过代码不可信任的驱动程序,除非加载它们的源与试图打开连接的代码的源相同。它通过轮流在每个驱动程序上调用方法
Driver.connect,并向它们传递用户开始传递给方法 DriverManager.getConnection 的 URL
来对驱动程序进行测试,然后连接第一个认出该 URL
的驱动程序。这种方法初看起来效率不高,但由于不可能同时加载数十个驱动程序,因此每次连接实际只需几个过程调用和字符串比较。以下代码是通常情况下用驱
动程序(例如 JDBC-ODBC 桥驱动程序)建立连接所需所有步骤的示例: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //加载驱动程序 String url = "jdbc:odbc:fred"; DriverManager.getConnection(url, "userID", "passwd"); 8.语句 ■Statement 对象概述
Statement 对象用于将 SQL 语句发送到数据库中。实际上有三种 Statement 对象,它们都作为在给定连接上执行 SQL
语句的包容器:Statement、PreparedStatement(它从 Statement 继承而来)和
CallableStatement(它从 PreparedStatement 继承而来)。它们都专用于发送特定类型的 SQL 语句:
Statement 对象用于执行不带参数的简单 SQL 语句;PreparedStatement 对象用于执行带或不带 IN 参数的预编译
SQL 语句;CallableStatement 对象用于执行对数据库已存储过程的调用。 Statement 接口提供了执行语句和获取结果的基本方法。PreparedStatement 接口添加了处理 IN 参数的方法;而 CallableStatement 添加了处理 OUT 参数的方法。 ■创建 Statement 对象 建立了到特定数据库的连接之后,就可用该连接发送 SQL 语句。Statement 对象用 Connection 的方法 createStatement 创建,如下列代码段中所示: Connection con = DriverManager.getConnection(url, "sunny", ""); Statement stmt = con.createStatement(); 为了执行 Statement 对象,被发送到数据库的 SQL 语句将被作为参数提供给 Statement 的方法: ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table2"); ■使用 Statement 对象执行语句 Statement 接口提供了三种执行 SQL 语句的方法:executeQuery、executeUpdate 和 execute。使用哪一个方法由 SQL 语句所产生的内容决定。 方法
executeQuery 用于产生单个结果集的语句,例如 SELECT 语句。
方法 executeUpdate 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如
CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE
语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于
CREATE TABLE 或 DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。 方法 execute 用于执行返回多个结果集、多个更新计数或二者组合的语句。 执行语句的所有方法都将关闭所调用的 Statement 对象的当前打开结果集(如果存在)。这意味着在重新执行 Statement 对象之前,需要完成对当前 ResultSet 对象的处理。
应注意,继承了 Statement 接口中所有方法的 PreparedStatement 接口都有自己的
executeQuery、executeUpdate 和 execute 方法。Statement 对象本身不包含 SQL 语句,因而必须给
Statement.execute 方法提供 SQL 语句作为参数。PreparedStatement 对象并不将 SQL
语句作为参数提供给这些方法,因为它们已经包含预编译 SQL 语句。CallableStatement 对象继承这些方法的
PreparedStatement 形式。对于这些方法的 PreparedStatement 或 CallableStatement
版本,使用查询参数将抛出 SQLException。 ■语句完成
当连接处于自动提交模式时,其中所执 行的语句在完成时将自动提交或还原。语句在已执行且所有结果返回时,即认为已完成。对于返回一个结果集的
executeQuery 方法,在检索完 ResultSet 对象的所有行时该语句完成。对于方法
executeUpdate,当它执行时语句即完成。但在少数调用方法 execute 的情况中,在检索所有结果集或它生成的更新计数之后语句才完成。 有些 DBMS 将已存储过程中的每条语句视为独立的语句;而另外一些则将整个过程视为一个复合语句。在启用自动提交时,这种差别就变得非常重要,因为它影响什么时候调用 commit 方法。在前一种情况中,每条语句单独提交;在后一种情况中,所有语句同时提交。 ■关闭 Statement 对象 Statement 对象将由 Java 垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要 Statement 对象时显式地关闭它们。这将立即释放 DBMS 资源,有助于避免潜在的内存问题。 9.结果设置
■概述
ResultSet 包含符合 SQL 语句中条件的所有行,并且它通过一套 get 方法(这些 get
方法可以访问当前行中的不同列)提供了对这些行中数据的访问。ResultSet.next 方法用于移动到 ResultSet
中的下一行,使下一行成为当前行。 ■行和光标 ResultSet
维护指向其当前数据行的光标。每调用一次 next 方法,光标向下移动一行。最初它位于第一行之前,因此第一次调用 next
将把光标置于第一行上,使它成为当前行。随着每次调用 next 导致光标向下移动一行,按照从上至下的次序获取 ResultSet 行。在
ResultSet 对象或其父辈 Statement 对象关闭之前,光标一直保持有效。在 SQL
中,结果表的光标是有名字的。如果数据库允许定位更新或定位删除,则需要将光标的名字作为参数提供给更新或删除命令。可通过调用方法
getCursorName 获得光标名。 注意:不是所有 DBMS支持定位更新和删除。可使用
DatabaseMetaData.supportsPositionedDelete 和 supportsPositionedUpdate
方法来检查特定连接是否支持这些操作。当支持这些操作时,DBMS/驱动程序必须确保适当锁定选定行,以使定位更新不会导致更新异常或其它并发问题。 ■列
方法 getXXX
提供了获取当前行中某列值的途径。在每一行内,可按任何次序获取列值。但为了保证可移植性,应该从左至右获取列值,并且一次性地读取列值。列名或列号可用
于标识要从中获取数据的列。例如,如果 ResultSet 对象 rs
的第二列名为“title”,并将值存储为字符串,则下列任一代码将获取存储在该列中的值: String s = rs.getString("title"); String s = rs.getString(2); 注意列是从左至右编号的,并且从列 1 开始。同时,用作 getXXX 方法的输入的列名不区分大小写。
提供使用列名这个选项的目的是为了让在查询中指定列名的用户可使用相同的名字作为 getXXX 方法的参数。另一方面,如果 select
语句未指定列名(例如在“select * from table1”中或列是导出的时),则应该使用列号。这些情况下,用户将无法确切知道列名。
有些情况下,SQL 查询返回的结果集中可能有多个列具有相同的名字。如果列名用作 getXXX 方法的参数,则 getXXX
将返回第一个匹配列名的值。因而,如果多个列具有相同的名字,则需要使用列索引来确保检索了正确的列值。这时,使用列号效率要稍微高一些。
■数据类型和转换
对于 getXXX 方法,JDBC 驱动程序试图将基本数据转换成指定 Java 类型,然后返回适合的 Java 值。例如,如果 getXXX
方法为 getString,而基本数据库中数据类型为 VARCHAR,则 JDBC 驱动程序将把 VARCHAR 转换成 Java
String。getString 的返回值将为 Java String 对象。可使用 ResultSet.getXXX 方法获取常见的
JDBC 数据类型。 准备语句 ■概述 该 PreparedStatement 接口继承 Statement,并与之在两方面有所不同: PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句“准备好”。
包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN 参数的值在 SQL
语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的
setXXX 方法来提供。 由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
作为 Statement 的子类,PreparedStatement 继承了 Statement
的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数占位符的值。同时,三种方法 execute、
executeQuery 和 executeUpdate 已被更改以使之不再需要参数。这些方法的 Statement 形式(接受 SQL
语句参数的形式)不应该用于 PreparedStatement 对象。 ■创建 PreparedStatement 对象 以下的代码段(其中 con 是 Connection 对象)创建包含带两个 IN 参数占位符的 SQL 语句的 PreparedStatement 对象: PreparedStatement pstmt = con.prepareStatement( "UPDATE table4 SET m = ? WHERE x = ?"); pstmt 对象包含语句 "UPDATE table4 SET m = ? WHERE x = ?",它已发送给 DBMS,并为执行作好了准备。 ■传递 IN 参数
在执行 PreparedStatement 对象之前,必须设置每个 ? 参数的值。这可通过调用 setXXX 方法来完成,其中 XXX
是与该参数相应的类型。例如,如果参数具有 Java 类型 long,则使用的方法就是 setLong。setXXX
方法的第一个参数是要设置的参数的序数位置,第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为 123456789,第二个参数设为
100000000: pstmt.setLong(1, 123456789); pstmt.setLong(2, 100000000); 一旦设置了给定语句的参数值,就可用它多次执行该语句,直到调用 clearParameters 方法清除它为止。 在连接的缺省模式下(启用自动提交),当语句完成时将自动提交或还原该语句。 如果基本数据库和驱动程序在语句提交之后仍保持这些语句的打开状态,则同一个 PreparedStatement 可执行多次。如果这一点不成立,那么试图通过使用 PreparedStatement 对象代替 Statement 对象来提高性能是没有意义的。
利用 pstmt(前面创建的 PreparedStatement 对象),以下代码例示了如何设置两个参数占位符的值并执行 pstmt 10
次。如上所述,为做到这一点,数据库不能关闭 pstmt。在该示例中,第一个参数被设置为 "Hi"并保持为常数。在 for
循环中,每次都将第二个参数设置为不同的值:从 0 开始,到 9 结束。 pstmt.setString(1, "Hi"); for (int i = 0; i < 10; i++) { pstmt.setInt(2, i); int rowCount = pstmt.executeUpdate(); } ■IN 参数中数据类型的一致性
setXXX 方法中的 XXX 是 Java 类型。它是一种隐含的 JDBC 类型(一般 SQL 类型),因为驱动程序将把 Java
类型映射为相应的 JDBC 类型(遵循该 JDBC Guide中§8.6.2 “映射 Java 和 JDBC 类型”表中所指定的映射),并将该
JDBC 类型发送给数据库。例如,以下代码段将 PreparedStatement 对象 pstmt 的第二个参数设置为 44,Java
类型为 short: pstmt.setShort(2, 44); 驱动程序将 44 作为 JDBC SMALLINT 发送给数据库,它是 Java short 类型的标准映射。
程序员的责任是确保将每个 IN 参数的 Java 类型映射为与数据库所需的 JDBC 数据类型兼容的 JDBC 类型。不妨考虑数据库需要
JDBC SMALLINT 的情况。如果使用方法 setByte ,则驱动程序将 JDBC TINYINT
发送给数据库。这是可行的,因为许多数据库可从一种相关的类型转换为另一种类型,并且通常 TINYINT 可用于 SMALLINT
适用的任何地方。然而,对于要适用于尽可能多的数据库的应用程序,最好使用与数据库所需的确切的 JDBC 类型相应的 Java 类型。如果所需的
JDBC 类型是 SMALLINT,则使用 setShort 代替 setByte 将使应用程序的可移植性更好。 ■ JDBC URL
JDBC URL 提供了一种标识数据库的方法,可以使相应的驱动程序能识别该数据库并与之建立连接。实际上,驱动程序编程员将决定用什么 JDBC
URL 来标识特定的驱动程序。用户不必关心如何来形成 JDBC URL;他们只须使用与所用的驱动程序一起提供的 URL 即可。JDBC
的作用是提供某些约定,驱动程序编程员在构造他们的 JDBC URL 时应该遵循这些约定。 (1) 由于 JDBC URL 要与各种不同的驱动程序一起使用,因此这些约定应非常灵活。它们应允许不同的驱动程序使用不同的方案来命名数据库。例如, odbc 子协议允许(但并不是要求) URL 含有属性值。 (2) JDBC URL 应允许驱动程序编程员将一切所需的信息编入其中。这样就可以让要与给定数据库对话的 applet 打开数据库连接,而无须要求用户去做任何系统管理工作。
(3) JDBC URL 应允许某种程度的间接性。也就是说,JDBC URL
可指向逻辑主机或数据库名,而这种逻辑主机或数据库名将由网络命名系统动态地转换为实际的名称。这可以使系统管理员不必将特定主机声明为 JDBC
名称的一部份。网络命名服务(例如 DNS、 NIS 和 DCE )有多种,而对于使用哪种命名服务并无限制。 JDBC URL
的标准语法如下所示。 它由三部分组成,各部分间用冒号分隔:jdbc:< 子协议 >:< 子名称 > JDBC URL 的三个部分可分解如下: jdbc ─ 协议。JDBC URL 中的协议总是 jdbc。
<子协议> ─ 驱动程序名或数据库连接机制(这种机制可由一个或多个驱动程序支持)的名称。子协议名的典型示例是
"odbc",该名称是为用于指定 ODBC 风格的数据资源名称的 URL 专门保留的。例如,为了通过 JDBC-ODBC
桥来访问某个数据库,可以用如下所示的 URL: jdbc:odbc:fred 本例中,子协议为 "odbc",子名称 "fred" 是本地 ODBC 数据资源。 如果要用网络命名服务(这样 JDBC URL 中的数据库名称不必是实际名称),则命名服务可以作为子协议。例如,可用如下所示的 URL : jdbc:dcenaming:accounts-payable 本例中,该 URL 指定了本地 DCE 命名服务应该将 数据库名称 "accounts-payable" 解析为更为具体的 可用于连接真实数据库的名称。
<子名称> ─
一种标识数据库的方法。子名称可以依不同的子协议而变化。它还可以有子名称的子名称(含有驱动程序编程员所选的任何内部语法)。使用子名称的目的是为定位
数据库提供足够的信息。前例中,因为 ODBC 将提供其余部份的信息,因此用 "fred"
就已足够。然而,位于远程服务器上的数据库需要更多的信息。例如,如果数据库是通过 Internet 来访问的,则在 JDBC URL
中应将网络地址作为子名称的一部份包括进去,且必须遵循如下所示的标准 URL 命名约定: //主机名:端口/子协议 假设 "dbnet" 是个用于将某个主机连接到 Internet 上的协议,则 JDBC URL 类似: jdbc:dbnet://wombat:356/fred ■ "odbc" 子协议 子协议 odbc 是一种特殊情况。它是为用于指定 ODBC 风格的数据资源名称的 URL 而保留的,并具有下列特性:允许在子名称(数据资源名称)后面指定任意多个属性值。odbc 子协议的完整语法为: jdbc:odbc:< 数据资源名称 >[;< 属性名 >=< 属性值 >]* 因此,以下都是合法的 jdbc:odbc 名称: jdbc:odbc:qeor7 jdbc:odbc:wombat
jdbc:odbc:wombat;CacheSize=20;ExtensionCase=LOWER jdbc:odbc:qeora;UID=kgh;PWD=fooey ■注册子协议
驱动程序编程员可保留某个名称以将之用作 JDBC URL 的子协议名。当 DriverManager
类将此名称加到已注册的驱动程序清单中时,为之保留该名称的驱动程序应能识别该名称并与它所标识的数据库建立连接。例如,odbc 是为 JDBC-
ODBC 桥而保留的。示例之二,假设有个 Miracle 公司,它可能会将 "miracle" 注册为连接到其 Miracle DBMS 上的
JDBC 驱动程序的子协议,从而使其他人都无法使用这个名称。 |
|
|