连接 JDBC
在本系列以前的文章中,通过使用 ij
工具连接 Apache Derby 数据库并与之进行交互,从而演示了许多数据库概念。可以让 Java 应用程序使用 JDBC 应用程序编程接口(API)连接嵌入式 Apache Derby 数据库并与之进行交互。在接下来的几篇文章中,您将学习如何通过编写自己的 Java 应用程序来重现 ij
工具的基本功能。本文主要关注建立数据库连接以及处理潜在的数据库错误和警告。
在开始编写 Java 代码之前,首先应该了解 JDBC API 的性质。JDBC 是正式的 Java Database Connectivity API,而且从 Java Development Kit 的 1.1 版本开始就存在了。JDBC API 包含在 java.sql
包中,如果仔细观察,您会发现这个 API 主要由接口组成。因此,创建数据库 JDBC 驱动程序的实际工作由数据库厂商(或第三方)负责,他们必须提供实现这些接口的 Java 类。javax.sql
包中的 JDBC API 扩展提供更高级的功能。下几期文章将讨论标准 JDBC 包的大多数部分;介绍完基本功能之后,再讨论扩展。
关于 JDBC 还有最后一点要注意:Java 应用程序和数据库之间的连接由 JDBC 驱动程序控制。原来有 4 种 JDBC 驱动程序类型,由它们的类型号区分:1、2、3 或 4。类型与 Java 应用程序和数据库进行通信所用的技术对应。当今的大多数驱动程序(包括用来连接 Derby 数据库的驱动程序)是 Type 4 驱动程序,这意味着它们是完全用 Java 语言编写的,它们直接将 JDBC API 转换为厂商特定的数据库协议。对于 Derby 数据库,这个过程就更简单了,因为 Derby 是用 Java 语言编写的!
Apache Derby 和 JDBC
既然您熟悉了 JDBC 的基本概念,就可以开始学习如何使用 Java 编程语言连接嵌入式 Apache Derby 数据库。但是首先,必须安装并运行 Apache Derby 软件,其过程见 系列的第一篇文章 。如果还没有执行这个关键的步骤,那么请阅读第一篇文章并下载和安装 Derby 软件。在安装 Derby 数据库软件之后,可以使用本文附带的示例代码连接 Derby 数据库,见清单 1。
清单 1. 执行示例代码
|
如果数据库无法运行,应该怎么办
尽管第一个数据库连接示例非常简单,但是有时候仍然可能出问题。在使用 Java 编程语言时,最可能出现的问题是必要的类文件不在
CLASSPATH
中。例如,如果代码已经编译了,但是在尝试运行它时出现错误消息
JDBC Driver org.apache.derby.jdbc.EmbeddedDriver not found in CLASSPATH
,那么就需要将 derby.jar 文件添加到
CLASSPATH
中。
在第一个示例中,创建一个用来开发和执行数据库应用程序代码的干净的工作空间。首先创建一个新目录,将本文提供的代码文件提取到这个目录中。在编译和执行之前,使用 ls
命令确认这个目录只包含本文中解释的 Java 源代码。
下一个步骤是使用 Java 编译器创建 Java 字节码文件,在下一步中 Java 虚拟机(Java Virtual Machine,JVM)将执行这个文件。如果成功地编译了源代码,就可以在 JVM 中执行字节码。这会调用 FirstConnect
类的 main
方法,生成与这里相似的输出;如果在这个过程中遇到了问题,请参见边栏 如果数据库无法运行,应该怎么办 。这段 Java 代码首先使用 Apache Derby 嵌入式 JDBC 驱动程序创建 test 数据库,然后创建到这个数据库的连接。为了演示这段代码如何处理 SQL 警告和错误,可以再次执行代码,这会首先显示警告消息,指出没有创建数据库(因为数据库已经存在),然后是与前面一样的关于数据库和 JDBC 驱动程序的标准信息。
在这个示例的结尾,再次显示工作目录的内容,这会显示编译后的 Java 类文件和新的数据库文件。最后一点很重要:在将 Apache Derby 用作嵌入式数据库时,数据库文件的默认位置是在代码所在的目录中。如果这不是您希望的位置,那么需要修改 JDBC URL 来指定应该创建数据库文件的位置。这个主题超出了本文的范围;请参考 Derby Developer's Guide (可以通过本文的 参考资料 部分中 Derby 在线手册的链接找到这个文档)。
连接 Java 应用程序和 Derby 数据库
前一节中执行的 Java 代码非常简单,本文的其余部分将详细讨论这些代码。在生产环境中,开发 Java 数据库应用程序可能很困难。本文并不涉及这些细节(这些将在以后的文章中讨论),而是主要关注在 Java 应用程序和嵌入式 Apache Derby 数据库之间建立连接的最基本的技术。这种技术(见清单 2)需要使用 JDBC 驱动程序实现连接协议。
清单 2. 使用 JDBC 连接 Derby 数据库
private static final String driver = "org.apache.derby.jdbc.EmbeddedDriver" ;
private static final String url = "jdbc:derby:test;create=true" ;
public static void main(String[] args) {
Connection con = null ;
DatabaseMetaData dbmd = null ;
try {
Class.forName(driver) ;
con = DriverManager.getConnection(url);
// Use the database connection somehow.
} catch (SQLException se) {
printSQLException(se) ;
} catch(ClassNotFoundException e){
System.out.println("JDBC Driver " + driver + " not found in CLASSPATH") ;
}finally {
if(con != null){
try{
con.close() ;
} catch(SQLException se){
printSQLException(se) ;
}
}
}
}
关于 DataSource
为了简单起见,本文只关注使用 Derby 嵌入式 JDBC 驱动程序连接嵌入式 Apache Derby 数据库。作为标准规则,连接数据库的首选 JDBC 解决方案是利用
DataSource
,这样就可以对数据库连接的细节进行抽象,比如数据库 URL、驱动程序类、用户名、密码和数据库名称。通过对特定的连接信息进行抽象,就很容易修改这些参数而不需要修改应用程序代码。以后的文章将讲解如何对 Derby 使用
DataSource
;但是首先了解 JDBC 驱动程序和
DriverManager
,然后再了解
DataSource
,这样更容易理解 JDBC 的基础知识。
这段示例代码首先定义两个常量,其中包含 Apache Derby 嵌入式 JDBC 驱动程序的 Java 类名和 JDBC 数据库连接 URL。这个连接 URL 应该很眼熟 —— 它与以前的文章中使用 ij
工具发出 connect
命令时使用的 URL 完全相同。赋给 driver
的值是嵌入式驱动程序类的完全限定名称。
代码的其余部分包含在 main
方法中;它使用 JVM 中的默认类装载器寻找并实例化前面定义的 driver
类(通过使用 Class.forname
方法)。这个方法寻找 Derby 嵌入式驱动程序的类文件(这个文件必须在 CLASSPATH
中存在),然后将它装载到 JVM 中。在装载过程中,会处理这个类的静态部分,这会向 JDBC DriverManager
对象注册这个驱动程序。
DriverManager
作为一个工厂对象。给出一个数据库 URL,DriverManager
就会使用适当的 JDBC 驱动程序返回一个数据库连接。这个步骤在下一行代码中执行,在这里使用前面定义的 URL 从 DriverManager
请求一个 JDBC connection
。
您肯定会注意到,连接请求被包围在 try ... catch
块中,而且在 finally
块中在连接上调用 close
方法。本文后面的 当出现错误时 一节会讨论这两个主题。由于篇幅的限制,本文无法全面讨论用 JDBC 建立数据库连接的不同方式。例如,这个示例没有使用任何安全信息,比如用户名或密码。以后的文章将研究其他数据库连接技术
数据库元数据
如果您从来没有遇到过元数据,那么它看起来似乎是一个奇怪的概念。但它实际上很简单:元数据(Metadata) 就是描述数据的数据。在数据库的上下文中,元数据描述特定的数据库,比如数据库的名称、它的版本号或建立连接的 JDBC 驱动程序的名称。通过调用 DatabaseMetaData
对象中的适当方法在 JDBC 中访问数据库元数据,见清单 3。
清单 3. 操作数据库元数据
DatabaseMetaData dbmd = null ;
dbmd = con.getMetaData() ;
System.out.println("\n----------------------------------------------------") ;
System.out.println("Database Name = " + dbmd.getDatabaseProductName()) ;
System.out.println("Database Version = " + dbmd.getDatabaseProductVersion()) ;
System.out.println("Driver Name = " + dbmd.getDriverName()) ;
System.out.println("Driver Version = " + dbmd.getDriverVersion()) ;
System.out.println("Database URL = " + dbmd.getURL()) ;
System.out.println("----------------------------------------------------") ;
如前面的示例所示,为了访问适当的元数据,首先要从当前的 JDBC Connection
创建一个新的 DatabaseMetaData
对象。然后,可以调用 Apache Derby JDBC 驱动程序提供的许多元数据函数(完整的列表参见本文 参考资料 链接的 JDBC 规范)。在这个示例中,检索了正在访问的数据库的名称和产品版本号、用来访问数据库的 JDBC 驱动程序的名称和版本号以及标识已经连接到的数据库的完整 JDBC URL。如果要开发的数据库应用程序必须与许多不同的数据库或 JDBC 驱动程序进行交互,那么数据库元数据是最有用的。在这种情况下,可以使用元数据在运行时判断特定数据库和 JDBC 驱动程序的能力。
出现错误时
在操作 Apache Derby 数据库时(无论是按照本系列以前文章的描述,还是采用自己的方法),您肯定遇到过数据库警告和数据库错误。由于在数据库中存储的信息的重要性,适当地处理 数据库错误和警告对于业务是很重要的。幸运的是,正如本节其余部分所演示的,这并不困难。第一步是学习如何查看提供给应用程序的关于数据库遇到的错误的信 息,见清单 4。
清单 4. 异常处理代码
private 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();
}
}
private 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();
}
}
如 清单 4 所示,处理 SQL 异常和 SQL 警告是相似的。SQLWarning
类继承自 SQLException
类;但是 SQL 警告不如 SQL 异常那么严重,所以更好的方法是分别处理它们。这两个函数都声明为 private static
,因此可以从 main
方法中调用它们,但是不能从其他类调用。在生产性代码中,可以根据程序的需求修改这一声明。
在这两个函数中,循环处理警告或异常。这在一般的错误处理场景中可能有点儿奇怪,但是在涉及数据库编程时就很常见了。出现多个警告或异常的原因很简单:在 数据库中事件常常连接在一起。例如,如果在数据库中插入多行,而且它们都由于列数据类型或名称不匹配而无法插入,那么就会有多个错误。为了正确地考虑所有 错误,需要能够将异常连接在一起,从而让进行调用的代码知道数据库遇到的所有问题。为了简化,Apache Derby 总是将最重要的异常放在异常链的开头。
在循环中,输出错误或警告信息。SQL 状态是一个五字符的字符串,它符合 X/OPEN Common Application Environment(CAE)规范、Data Management: SQL, Version 2 SQL 规范或 SQL99 标准约定,使程序能够从特定的数据库错误中恢复过来。SQL 状态的前两个字符是一个类值,后三个字符形成子类值。SQL 状态为 00000
值就表示成功,类值为 01
就表示警告,比如数据截断。SQL 编码是与数据库相关的值。
处理 SQL 异常很简单。使用标准的 Java try ... catch
结构包围 JDBC 方法调用,如清单 5 所示。
清单 5. 捕获 SQL 异常
try{
// Execute a JDBC operation
} catch (SQLException se) {
printSQLException(se) ;
}
如这个示例所示,通过将 SQLException
对象传递给前面定义的 printSQLException
方法来处理任何 SQL 异常,这会处理所有错误报告。在处理 SQL 异常时,惟一的难点是正确地回收数据库资源,比如数据库连接。因为可以在运行数据库应用程序代码的 JVM 之外管理连接或数据库游标这样的资源,所以应用程序必须显式地关闭这些资源。Connection
对象的 close
方法可以抛出 SQLException
,所以要将这个方法放在 finally
块中。这样的话,即使发生了错误,Java 应用程序也会尝试关闭数据库资源。
另一方面,必须显式地检查任何 SQL 警告,因为它们不会通过标准的异常处理机制传播。为此,需要调用相关 JDBC 对象上的适当 getWarnings
方法,见清单 6。
清单 6. 检查 SQL 警告
正如前面所讨论的,SQL 警告由 printSQLWarning
方法处理。在调用这个方法之前,首先要检查是否至少有一个 SQL 警告;如果有,就将第一个 SQLWarning
对象传递给适当的方法。在以后的文章中会看到,许多 JDBC 对象都可以产生 SQL 警告,所以将错误和警告处理代码封装起来可以简化数据库应用程序代码的开发和维护。
将所有代码组合起来
到目前为止,本文已经讨论了使用 JDBC API 和 Apache Derby 建立数据库连接所需的许多组件。清单 7 中提供了完整的连接示例,它说明这些组件组合在一起可以提供大多数必要的功能。
清单 7. 完整的连接示例代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.DatabaseMetaData;
public class FirstConnect {
private static final String driver = "org.apache.derby.jdbc.EmbeddedDriver" ;
private static final String url = "jdbc:derby:test;create=true" ;
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();
}
}
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) {
Connection con = null ;
DatabaseMetaData dbmd = null ;
try {
Class.forName(driver) ;
con = DriverManager.getConnection(url);
SQLWarning swarn = con. getWarnings() ;
if(swarn != null){
printSQLWarning(swarn) ;
}
dbmd = con.getMetaData() ;
System.out.println("\n----------------------------------------------------") ;
System.out.println("Database Name = " + dbmd.getDatabaseProductName()) ;
System.out.println("Database Version = " + dbmd.getDatabaseProductVersion()) ;
System.out.println("Driver Name = " + dbmd.getDriverName()) ;
System.out.println("Driver Version = " + dbmd.getDriverVersion()) ;
System.out.println("Database URL = " + dbmd.getURL()) ;
System.out.println("----------------------------------------------------") ;
} catch (SQLException se) {
printSQLException(se) ;
} catch(ClassNotFoundException e){
System.out.println("JDBC Driver " + driver + " not found in CLASSPATH") ;
}
finally {
if(con != null){
try{
con.close() ;
} catch(SQLException se){
printSQLException(se) ;
}
}
}
}
}
这个类是通过 JDBC 连接嵌入式 Apache Derby 数据库所需的所有东西,它提供的功能与本文开头编译和执行的示例代码完全相同。主要的新代码是程序代码开头的五个 import 语句。尽管可以使用 import java.sql.*
,但是单独列出每个 import 语句可以明确地表明在应用程序中要使用哪些 JDBC 对象。
这个应用程序包含 清单 4 中的错误处理函数。所有数据库操作都包围在一个 try ... catch
块中,包括 清单 3 所示的数据库元数据操作。许多数据库应用程序都避免数据库应用程序代码和数据库之间的这种耦合,从而简化应用程序和数据库的管理和维护。但是由于 Apache Derby 数据库的嵌入式功能,应用程序和数据库之间的联系比较模糊,并不需要严格实施隔离。