本章内容包括:
● 学习如何使用JDBC API在Java程序中运行SQL。
● 介绍可用来连接Oracle数据库的各种Oracle JDBC驱动程序。
● 执行查询和SQL DML语句来访问数据库表。
● 学习如何使用各种Java类型来获得和设置数据库中的列值。
● 介绍如何执行事务控制语句和SQL DDL语句。
● 处理运行Java程序时可能发生的数据库异常。
● 介绍Oracle数据库软件对JDBC的扩展。
● 用完整的Java程序来说明JDBC的使用。
注意:
本章只简单介绍JDBC。关于在Oracle数据库中使用JDBC的详细信息,请参阅本书作者编写的另一本书Oracle9i JDBC Programming(McGraw-Hill/Osborne,2002)。
15.1 准备工作
在运行本章的示例之前,需要安装Sun公司的Java软件开发工具包(Software Development Kit,SDK)。可以从Sun的Java网站java.sun.com上下载SDK,并查看完整的安装说明。
注意:
编写本章时,我用的是Java 1.6.0,与它一起安装的是Java EE 5 SDK Update 2。
在计算机上安装Oracle软件的目录称为ORACLE_HOME目录。在我的Windows计算机上,此目录是E:\oracle_11g\product\11.1.0\db1。ORACLE_HOME目录中包含很多子目录,其中之一是jdbc目录,此目录包含下列内容:
● 一个文本文件Readme.txt。应该打开并阅读该文件,因为它包含重要信息,比如版本信息和最新的安装指导。
● 一个lib目录,它包含许多Java Archive(JAR)文件。
15.2 配置计算机
下载和安装所需软件之后,下一步就是配置计算机,以便开发和运行包含JDBC语句的Java程序。必须在计算机上设置4个环境变量:
● ORACLE_HOME;
● JAVA_HOME;
● PATH;
● CLASSPATH。
如果使用的是Unix或Linux,另外还需要设置环境变量LD_LIBRARY_PATH。后面将介绍如何设置这些环境变量。
警告:
编写本节内容时,这些信息是正确的。但您需要阅读ORACLE_HOME\jdbc目录下的Readme.txt文件,以检查最新的版本信息和安装说明。
15.2.1 设置ORACLE_HOME环境变量
ORACLE_HOME子目录是安装Oralce软件的位置,还需要在计算机上设置一个环境变量ORACLE_HOME,指定该目录。
1. 在Windows XP中设置环境变量
要在Windows XP中设置环境变量,需要执行下列步骤:
(1) 打开Control Panel。
(2) 双击System,显示出System Properties对话框。
(3) 选择Advanced标签。
(4) 单击Environment Variables按钮,显示出Environment Variables对话框。
(5) 在System Variables区域(对话框的下部窗格),单击New按钮。
(6) 设置变量名为ORACLE_HOME,设置值为您的ORACLE_HOME目录。(在我的Windows XP机器上,ORACLE_HOME被设置为E:\oracle_11g\product\11.1.0\db1)。
2. 在Unix或Linux中设置环境变量
在Unix或Linux中设置环境变量,需要在一个特定文件中添加行,需要更改的文件取决于所使用的shell。如果使用的shell是Bourne、Korn或Bash,则需要将类似于下面的代码行添加到.profile(使用的shell是Bourne或Korn时)或.bash_profile(使用的shell是Bash时)文件中:
ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1 |
注意:
需要用正确的ORACLE_HOME安装目录替换上面给出的目录。
如果使用的是C shell,则需将下面的代码行添加到.login文件中:
setenv ORACLE_HOME /u01/app/oracle/product/11.1.0/db_1 |
15.2.2 设置JAVA_HOME环境变量
JAVA_HOME环境变量指定安装Java SDK的目录。例如,如果将Java SDK安装在E:\java\jdk目录下,就需要创建一个JAVA_HOME系统变量,并将它设置为该目录,步骤如上一节所示
15.2.3 设置PATH环境变量
PATH环境变量包含一个目录列表。当在操作系统命令行中输入一个命令时,计算机会在PATH的目录中查找试图运行的命令。需要在现有的PATH中添加下面两个目录:
● 安装Java SDK的bin子目录。
● ORACLE_HOME的BIN子目录。
例如,如果将Java SDK安装在E:\java\jdk目录中,ORACLE_HOME是E:\oracle_11g\ product\11.1.0\db1,则需要将E:\java\jdk\bin; E:\oracle_11g\product\11.1.0\db1添加到PATH中。注意,分号";"用于隔开两个目录。设置Windows XP的PATH,步骤与上述步骤相似。
在Unix或Linux的现有PATH中添加目录,需要更改shell中相应的文件。例如,如果使用的是Linux的Bash shell,则要将如下代码行添加到.bash_profile文件中:
PATH=$PATH:$JAVA_HOME/bin:$ORACLE_HOME/BIN |
注意,分号";"用于隔开目录。
15.2.4 设置CLASSPATH环境变量
CLASSPATH环境变量包含一个位置列表,这些位置用于存放Java类包。位置可以是一个目录名,也可以是包含类的Zip文件或JAR文件的名称。ORACLE_HOME\jdbc\lib目录包含许多JAR文件;添加到CLASSPATH中的JAR文件取决于所使用的Java SDK。
编写本书时,设置CLASSPATH的规则如下:
● 如果使用的是JDK 1.6(或更高版本),将ORACLE_HOME\jdbc\lib\ojdbc6.jar添加到CLASSPATH中。
● 如果使用的是JDK 1.5,将ORACLE_HOME\jdbc\lib\ojdbc5.jar添加到CLASSPATH中。
● 如果需要国家语言支持,还要将ORACLE_HOME\jlib\orai18n.jar添加到CLASSPATH中。
● 如果需要JTA和JNDI特性,则需要将ORACLE_HOME\jlib\jta.jar和ORACLE_HOME\ jlib\jndi.jar添加到CLASSPATH中。JNDI是Java Naming and Directory Interface的缩写。JTA是Java Transaction API的缩写。
● 另外还需要将当前目录添加到CLASSPATH中,通过将一个句点"."添加到CLASSPATH中完成这一操作。这样,当运行程序时,Java就会找到当前目录中的类。
当使用Java 1.6,且ORACLE_HOME是E:\oracle_11g\product\11.1.0\db1时,Windows XP中的一个CLASSPATH示例是:
.;E:\oracle_11g\product\11.1.0\db1\jdbc\lib\ojdbc6.jar; |
如果使用的是Windows XP,使用前面所示的步骤创建系统环境变量CLASSPATH。如果使用的是Linux和Java 1.6,应将如下代码行添加到.bash_profile文件中。
CLASSPATH=$CLASSPATH:.:$ORACLE_HOME/jdbc/lib/ojdbc6.jar: |
15.2.5 设置LD_LIBRARY_PATH环境变量
如果使用的是Unix或Linux,还需要将LD_LIBRARY_PATH环境变量设置为$ORACLE_ HOME/jdbc/lib。该目录包含JDBC OCI驱动程序使用的共享库。需要将LD_LIBRARY_PATH添加到合适的文件中,例如:
LD_LIBRARY_PATH=$ORACLE_HOME/jdbc/lib |
配置计算机完成之后,下面来看Oracle JDBC驱动程序。
15.3 Oracle JDBC驱动程序
本节介绍各种Oracle JDBC驱动程序,它们允许Java程序中的JDBC语句访问Oracle数据库。Oracle JDBC驱动程序有4种:
● Thin驱动程序
● OCI驱动程序
● 服务器端内部驱动程序
● 服务器端Thin驱动程序
下面各小节分别介绍这些驱动程序。
15.3.1 Thin驱动程序
Thin驱动程序是占用内存最小的驱动程序,也就是说运行它所需的系统资源最少,它全部是用Java编写的。如果编写一个Java applet,应该使用Thin驱动程序。Thin驱动程序还可以用于独立Java应用程序,并用于访问所有版本的Oracle数据库。Thin驱动程序只支持TCP/IP,并要求启动和运行Oracle Net。关于Oracle Net的详细信息,可以参阅由Oracle公司出版的Oracle Database Net Services Administrator's Guide。
注意:
使用Thin驱动程序不需要在客户计算机上安装任何软件,因此它可以用于applet。
15.3.2 OCI驱动程序
OCI驱动程序需要的资源比Thin驱动程序的要多,但是通常具有更好的性能。OCI驱动程序适合部署在中间层(例如,Web服务器)上的程序。
注意:
OCI驱动程序需要安装在客户计算机上,所以不适用于applet。
OCI驱动程序具有许多性能增强的特性,包括数据库连接池、从数据库中预取行。OCI驱动程序支持所有版本的数据库和所有支持的Oracle Net协议。
15.3.3 服务器端内部驱动程序
服务器端内部驱动程序提供对数据库的直接访问,Oracle JVM使用该驱动程序与数据库通信。Oracle JVM是一个Java虚拟机(Java Virtual Machine),它与数据库集成。可以将Java类加载到数据库中,然后使用Oracle JVM发布并运行该类包含的方法。Java代码运行在数据库服务器上,并可以从单一的Oracle会话访问数据。
15.3.4 服务器端Thin驱动程序
服务器端Thin驱动程序也用于Oracle JVM,提供对远程数据库的访问。和Thin驱动程序一样,服务器端Thin驱动程序也完全用Java编写。使用服务器端Thin驱动程序的Java代码可以访问相同的数据库服务器或某个远程服务器上的另一个会话。
15.4 导入JDBC包
要在Java程序中使用JDBC,必须把所需的JDBC包导入程序中。JDBC包有两套:
● Sun Microsystems的标准JDBC包。
● Oracle公司的扩展包。
标准JDBC包允许Java程序访问大多数数据库的基本功能,包括Oracle数据库、SQL Server、DB2和MySQL。Oracle的JDBC扩展包允许程序访问所有Oracle特有的功能和Oracle特有的性能扩展。稍后本章会介绍一些Oracle特有的功能。
要在程序中使用JDBC,必须导入java.sql.*包,如以下import语句所示:
import java.sql.*; |
当然,导入java.sql.*就导入了所有的标准JDBC包。当读者熟悉JDBC后,会发现并不需要导入所有的类,只导入程序实际使用的包即可。
15.5 注册Oracle JDBC驱动程序
在打开数据库连接之前,必须首先在Java程序注册Oracle JDBC驱动程序。前文已提及,JDBC驱动程序允许JDBC语句访问数据库。
注册Oracle JDBC驱动程序有两种方法:
● 使用java.lang.Class类中的forName()方法
● 使用JDBC DriverManager类中的registerDriver()方法
下例显示了forName()方法的用法:
Class.forName("oracle.jdbc.OracleDriver"); |
第二种注册Oracle JDBC驱动程序的方法是使用java.sql.DriverManager类中的registerDriver()方法,如下例所示:
DriverManager.registerDriver(new oracle.jdbc.OracleDriver()); |
注册Oracle JDBC驱动程序完成之后,就可以打开数据库连接。
15.6 打开数据库连接
在Java程序中执行SQL语句之前,必须打开数据库连接。打开数据库连接主要有两种方法:
● 使用DriverManager类中的getConnection()方法
● 使用Oracle数据源对象,首先创建它,然后连接它。该方法使用了设置数据库连接详细信息的标准化方法,而且Oracle数据源对象可以和JNDI(Java Naming and Directory Interface)一起使用。
下面介绍用这两种打开数据库连接的方法,首先介绍使用DriverManager类的getConnection()方法。
15.6.1 使用getConnection()方法连接数据库
getConnection()方法返回一个JDBC Connection对象,应该把它存储在程序中,以便以后引用。调用getConnection()方法的语法如下:
DriverManager.getConnection( URL, username, password); |
其中,
● URL是程序要连接的数据库和要使用的JDBC驱动程序。(下一节"数据库URL"中将详细介绍URL。)
● username是程序连接时所用的数据库用户名。
● password是该用户名的密码。
下例显示了用于连接数据库的getConnection()方法:
Connection myConnection = DriverManager.getConnection( |
本例中,所连接的数据库运行在标识为localhost的计算机上,Oracle系统标识符(System Identifier,SID)为ORCL,使用的是Oracle JDBC Thin驱动程序。连接使用的用户名是store,密码为store_password。getConnection()调用返回的Connection对象存储在myConnection中。数据库连接通过Oracle Net进行,所以必须启动并运行它。
15.6.2 数据库URL
数据库URL指定程序连接的数据库位置。数据库URL的结构取决于提供JDBC驱动程序的供应商。如果使用的是Oracle的JDBC驱动程序,数据库URL结构如下:
driver_name:@driver_information |
其中,
● driver_name是程序使用的Oracle JDBC驱动程序的名称。它可以设置为其中之一:
● jdbc:oracle:thin,是Oracle JDBC Thin驱动程序。
● jdbc:oracle:oci,是Oracle JDBC OCI驱动程序。
● driver_information 连接数据库所需的特定于驱动程序的信息。它取决于所使用的驱动程序。如果使用的是Oracle JDBC Thin驱动程序,特定于驱动程序的信息应按如下格式设定:
● host_name:port:database_SID,其中host_name是计算机名称,port是访问数据库所用的端口,database_SID是数据库SID。
对于所有Oracle JDBC驱动程序,包括Thin驱动程序和各种OCI驱动程序,特定于驱动程序的信息还可以使用Oracle Net键值对指定,格式如下:
(description=(address=(host= host_name)(protocol=tcp)(port= port)) |
其中
● host_name是运行数据库的计算机名称。
● port是Oracle Net数据库监听器等待请求的端口号;1521是默认端口号。DBA可以提供端口号。
● database_SID是要连接的数据库实例的Oracle SID。DBA可以提供数据库SID。
下例显示了getConnection()方法,它使用Oracle OCI驱动程序来连接数据库,使用Oracle Net键值对指定特定于驱动程序的信息:
Connection myConnection = DriverManager.getConnection( |
本例中,所连接的数据库运行在标识为localhost的计算机上,ORCL为Oracle SID,使用的是Oracle OCI驱动程序。数据库连接使用用户名store,密码store_password。getConnection()调用返回的Connection对象存储在myConnection中。
注意:
对于Oracle OCI驱动程序,可以使用Oracle Net TNSNAMES字符串。要获得更多信息,请咨询DBA或参阅由Oracle公司出版的Oracle Database Net Services Administrator's Guide。
15.6.3 使用Oracle数据源连接数据库
还可以使用Oracle数据源连接数据库。Oracle数据源使用了一个比使用DriverManager.getConnection()方法的方法更标准化的方法,它提供了多种参数来连接数据库。而且,Oracle数据源也使用JNDI注册。将JNDI用于JDBC是非常有用的,因为它允许注册或绑定数据源,然后在程序中查找数据源,而不需要提供完整的数据库连接详细信息。因此,如果数据库连接详细信息改变了,只需要修改JNDI对象即可。
注意:
学习JNDI,可以参考我的书Oracle 9i JDBC Programming(McGraw-Hill/Osborne,2002)。
使用Oracle数据源需要执行下列3个步骤:
(1) 创建oracle.jdbc.pool.OracleDataSource 类的Oracle数据源对象。
(2) 使用类中定义的set方法,设置Oracle数据源对象属性。
(3) 使用getConnection()方法,通过Oracle数据源对象连接数据库。
下面描述这3个步骤。
步骤1:创建Oracle数据源对象 第一步创建oracle.jdbc.pool.OracleDataSource类的数据源对象。下例创建了OracleDataSource对象myDataSource(假定已经导入oracle.jdbc.pool.Oracle DataSource类):
OracleDataSource myDataSource = new OracleDataSource(); |
创建了OracleDataSource对象之后,第二步使用set方法设置该对象的属性。
步骤2:设置Oracle数据源对象属性 使用OracleDataSource对象连接数据库之前,必须使用类中定义的各种set方法设置对象的多个属性来指定连接详细信息。这些详细信息包括数据库名称、要使用的JDBC驱动程序,等等;每个详细信息对应OracleDataSource对象的一个属性。
oracle.jdbc.pool.OracleDataSource类实际上实现了JDBC提供的javax.sql.DataSource接口。javax.sql.DataSource接口定义了许多属性,见表15-1。表15-1显示了每个属性的名称、描述和类型。
表15-1 DataSource属性
属 性 名 称 | 属 性 描 述 | 属 性 类 型 |
databaseName | 数据库名称(Oracle SID) | String |
dataSourceName | 底层数据源类的名称 | String |
description | 数据源的描述 | String |
networkProtocol | 用于与数据库通信的网络协议。它仅应用于Oracle JDBC OCI驱动程序,默认值为tcp。更多详细信息参阅Oracle公司出版的Oracle Database Net Services Administrator’s Guide | String |
password | 所提供的用户名的密码 | String |
portNumber | Oracle Net监听器等待数据库连接请求的端口。默认值为1521 | int |
serverName | 数据库服务器计算机名称(TCP/IP地址或DNS别名) | String |
user | 数据库用户名 | String |
oracle.jdbc.pool.OracleDataSource类提供属性的附加集合,见表15-2。
表15-2 OracleDataSource属性
属 性 名 称 | 属 性 描 述 | 属 性 类 型 |
driverType | 所使用的JDBC驱动程序。如果使用的是服务器内部驱动程序,设置为kprb。忽略属性的其他设置 | String |
url | 可用于指定Oracle数据库URL,也可用于设置数据库位置。详细信息见前面的“数据库URL”一节 | String |
tnsEntryName | 可用于指定Oracle Net TNSNAMES字符串,当使用OCI驱动程序时,也可用于指定数据库位置 | String |
可以使用许多方法读取和写入表15-1和表15-2中列出的每个属性。读取属性的方法称为get方法,写入属性的方法称为set方法。
set方法和get方法的名称很容易记住:获取属性名,将属性名的第一个字符转换为大写,在前面加上set或get。例如,设置数据名(存储在databaseName属性中),使用setDatabaseName()方法;获取当前设置的数据库名称,使用getDatabaseName()方法。但有一个例外:不存在可供调用的getPassword()方法。这是出于安全的考虑-- 以免其他人编写程序获取他人密码。
大多数属性是Java String对象,所以大多数set方法接受单个String参数,大多数get方法返回一个String。portNumber属性是个例外,它是一个int。因此,它的set方法setPortNumber()接受一个int,它的get方法getPortNumber()返回一个int。
下例显示了set方法的用法,写入前面步骤1中创建的OracleDataSource对象myDataSource的属性:
myDataSource.setServerName("localhost");myDataSource.setDatabaseName("ORCL");myDataSource.setDriverType("oci");myDataSource.setNetworkProtocol("tcp");myDataSource.setPortNumber(1521);myDataSource.setUser("scott");myDataSource.setPassword("tiger"); |
下例显示了一些get方法的用法,读取前面设置的myDataSource的属性:
String serverName = myDataSource.getServerName();String databaseName = myDataSource.getDatabaseName();String driverType = myDataSource.getDriverType();String networkProtocol = myDataSource.getNetworkProtocol();int portNumber = myDataSource.getPortNumber(); |
设置完OracleDataSource对象的属性之后,就可以使用它连接数据库。
步骤3:通过Oracle数据源对象连接数据库 第三步是通过OracleDataSource对象连接数据库。使用OracleDataSource对象调用getConnection()方法,就可完成该操作。getConnection()方法返回一个JDBC Connection对象,该对象必须存储起来。
下例显示如何使用上一步填充的myDataSource对象调用getConnection()方法:
Connection myConnection = myDataSource.getConnection(); |
getConnection()返回的Connection对象存储在myConnection中。也可以把用户名和密码作为参数传递给getConnection()方法,如下所示:
Connection myConnection = myDataSource.getConnection("store", "store_password"); |
在本例中,用户名和密码会覆盖myDataSource中以前设置的的用户名和密码。因此,数据库连接是使用用户名store和密码store_password,而不是scott和tiger,它们是上一节设置在myDataSource中的。
有了Connection对象,就可以使用它来创建JDBC Statement对象。
15.7 创建JDBC Statement对象
接下来需要在类java.sql.Statement中创建一个JDBC Statement对象。一个Statement对象用于表示一个SQL语句,比如查询、DML语句(INSERT、UPDATE或DELETE)或DDL语句(比如CREATE TABLE)。本章稍后会介绍如何发布查询、DML和DDL语句。
要创建Statement对象,需要使用Connection对象的createStatement()方法。在下例中,使用前面创建的myConnection对象的createStatement()方法,创建一个Statement对象myStatement:
Statement myStatement = myConnection.createStatement(); |
根据要执行的SQL语句,使用Statement类中不同的方法来运行SQL语句。如果要执行查询语句,使用executeQuery()方法。如果要执行INSERT、UPDATE或DELETE语句,使用executeUpdate()方法。如果事先不知道要执行的SQL语句的类型,可以使用execute()方法,它可以执行任何SQL语句。
还有另一个JDBC类可用于表示SQL语句:PreparedStatement类。它提供了比Statement类更高级的功能;讨论了Statement类的用法之后,讨论PreparedStatement类。
创建了Statement对象之后,就可以使用JDBC执行SQL语句。
15.8 从数据库中检索行
要使用JDBC执行查询,可以使用Statement对象的executeQuery()方法,它接受一个Java String,其中包含查询的文本。
因为一个查询可能返回多行,所以executeQuery()方法返回一个对象,它存储查询返回的行。该对象称为JDBC结果集(result set),属于java.sql.ResultSet类。当使用ResultSet对象从数据库读取行时,要执行3个步骤:
(1) 创建一个ResultSet对象,使用查询返回的结果填充它。
(2) 使用get方法从ResultSet对象中读取列值。
(3) 关闭ResultSet对象。
现在介绍一个例子,使用ResultSet对象从customers表中检索行。
15.8.1 步骤1:创建和填充ResultSet对象
首先必须创建ResultSet对象,并使用查询返回的结果填充它。下例创建ResultSet对象customerResultSet,并使用customers表的customer_id、first_name、last_name、dob和phone列填充它:
ResultSet customerResultSet = myStatement.executeQuery( |
运行该语句之后,ResultSet对象包含了查询检索到的行的列值。然后,可以使用ResultSet对象访问检索到的行的列值。本例中,customerResultSet包含从customers表检索到的5行。
因为execute()方法接受一个Java String,所以可以在程序实际运行时添加SQL语句。这就是说,可以在JDBC中实现一些相当强大的操作。例如,当用户运行程序时,可以允许他们为查询输入一个包含WHERE子句的字符串,甚至输入整个查询。下例展示了一个WHERE子句字符串:
String whereClause = "WHERE customer_id = 1"; |
不仅可以动态地添加查询:还可以以同样的方法添加其他的SQL语句。
15.8.2 步骤2:从ResultSet对象中读取列值
要读取存储在ResultSet中的行的列值,ResultSet类提供了一系列get方法。在详细介绍这些get方法之前,需要理解在Oracle中用于表示值的数据类型如何映射为兼容的Java数据类型。
1. Oracle和Java类型
Java程序中用于表示值的类型与Oracle类型不同。所幸的是,Oracle使用的类型兼容某些Java类型。这样Java和Oracle就可以交换以各自类型存储的数据。表15-3显示了一组兼容类型的映射。
表15-3 兼容类型的映射
从表15-3中可以看到Oracle INTEGER兼容Java int(稍后在15.12节"处理数字"中介绍其他数字类型)。这样,customers表的customer_id列(定义为Oracle INTEGER类型)就可以存储在Java int变量中。同样地,first_name、last_name和phone的列值(VARCHAR2类型)可以存储在Java String变量中。
Oracle DATE类型存储年、月、日、小时、分和秒。可以使用java.sql.Date对象存储dob列值的日期部分,使用java.sql.Time变量存储时间部分。还可以使用java.sql.Timestamp对象存储dob列的日期和时间部分。稍后本章会讨论oracle.sql.DATE类型,它是Oracle对JDBC标准的一个扩展,提供了存储日期和时间的一种很不错的方法。
前面的查询检索了customer_id、first_name、last_name、dob和phone列,下例声明与这些列兼容的Java变量和对象:
int customerId = 0; |
int和String类型是核心Java语言的一部分,而java.sql.Date是JDBC的一部分,是对核心Java语言的一个扩展。JDBC提供了许多类型,允许Java和关系数据库交换数据。但是,JDBC并不包含一种能处理所有Oracle类型的类型,其中一个例子是ROWID类型--必须使用oracle.sql.ROWID类型存储Oracle ROWID。
为了处理所有Oracle类型,Oracle提供了许多附加类型,定义在oracle.sql包中。而且,Oracle有许多类型可以用作核心Java和JDBC类型的备用类型,有时候,这些备用类型提供了比核心Java和JDBC类型更多的功能和更好的性能。稍后本章会介绍oracle.sql包中定义的Oracle类型。
现在我们已经理解了兼容的Java和Oracle类型,接下来讨论使用get方法读取列值。
2. 使用get方法读取列值
前已提及,get方法用于读取存储在ResultSet对象中的值。每个get方法的名称很容易理解:取出想要返回的列值Java类型的名称,并在其前面加上get这个单词。例如,使用getInt()将列值读取为Java int,使用getString()将列值读取为Java String。要将列值读取为java.sql.Date,需要使用getDate()。每个get方法接受一个参数:int或者String,int表示原来的查询中列的位置,String包含列的名称。前面的例子中,检索了customerResultSet对象中的customers表的列,下面再看几个例子。
要获取customer_id的列值,它是前面的查询指定的第一列,所以可以使用getInt(1)。也可以在get方法中使用列名,所以可以使用getInt("customer_id")获取相同的值。
提示:
在get方法中使用列名而不是列位置号,可以增加代码的可读性。
为了获取first_name的列值,它是前面的查询指定的第二列,可以使用getString(2)或getString("first_name")。获取last_name和phone列值的方法与此类似,因为这些列也是文本字符串。为了获取dob列的值,可以使用getDate(4)或getDate("dob")。实际读取存储在ResultSet对象中的值,必须用ResultSet对象调用get方法。
因为ResultSet对象可以包含多行,所以JDBC提供了方法next(),它允许遍历ResultSet对象存储的每一行。必须调用next()方法访问ResultSet对象中的第一行,接下来每调用一次next()就向下移动一行。当ResultSet对象中没有行可读取时,next()方法返回布尔型false值。
现在返回到例子中:ResultSet对象customerResultSet有5行,它们是从customers表的customer_id、first_name、last_name、dob和phone列中检索到的列值。下例显示了一个while循环,它读取customerResultSet的列值,写入前面创建的customerId、firstName、lastName、dob和phone对象中,如下所示:
while (customerResultSet.next()) { System.out.println("customerId = " + customerId); |
当customerResultSet中没有行可读取时,next()方法返回false,循环终止。注意,在例子中,传递给get方法的是要读取的列的名称,而不是数字位置。而且,列值被复制到Java变量和对象中,例如,customerResultSet.getInt("customer_id")返回的值被复制到customerId中。不一定要进行复制,当需要值时可以仅使用get方法调用。但是,一般来说,如果将值复制到Java变量或对象中更好,如果频繁地使用值,由于不必再次调用get方法,所以会节省时间。
15.8.3 步骤3:关闭ResultSet对象
使用完ResultSet对象之后,必须使用close()方法关闭ResultSet对象。下例关闭
customerResultSet: |
注意:
使用完ResultSet对象之后,一定要记住关闭它,这一点很重要。这样可以确保及时地收集对象无用的存储单元。
明白了如何检索行之后,下面介绍如何使用JDBC向数据库表添加行。
15.9 向数据库中添加行
SQL INSERT语句用于向表中添加行。使用JDBC执行INSERT语句有两种主要方法:
● 使用Statement类中定义的executeUpdate()方法。
● 使用PreparedStatement类(本章稍后会介绍该类)中定义的execute()方法
本节的例子显示如何向customers表添加行。这个新行的customer_id、first_name、last_name、dob和phone列分别设置为6、Jason、Price、February 22, 1969和800-555-1216。
为了添加这个新行,使用前面声明的Statement对象myStatement,以及相同的变量和对象,在上一节中它们用于检索customers表的行。首先,将这些变量和对象设置为想要在customers表的数据库列设置的值。
customerId = 6; |
注意:
java.sql.Date类存储日期,使用格式YYYY-MM-DD,其中YYYY是年,MM是月,DD是日。还可以使用java.sql.Time和java.sql.Timestamp类分别表示时间和包含时间的日期。
如果要在SQL语句中指定一个日期,必须首先使用TO_DATE()内置数据库函数,把它转换为数据库可以理解的格式。TO_DATE()函数接受一个包含日期的字符串和日期的格式。在后面的INSERT语句例子中,显示了TO_DATE()函数的用法。稍后本章会介绍Oracle JDBC扩展,并介绍一种很不错的用oracle.sql.DATE类型表示Oracle特有日期的方法。
现在,准备执行INSERT语句向customers表添加新行。myStatement对象用于执行INSERT语句,将customer_id、first_name、last_name、dob和phone的列值设置为与前面的customerId、firstName、lastName、dob和phone变量相同的值。
myStatement.executeUpdate( |
注意,TO_DATE()函数用于将dob对象的内容转换为可接受的Oracle数据库日期。该语句完成之后,customers表就包含了这个新行。
15.10 更改数据库的行
SQL UPDATE语句用于更改表的现有行。和使用JDBC执行INSERT语句一样,可以使用Statement类中定义的executeUpdate()方法或PreparedStatement类中定义的execute()方法。
PreparedStatement类的用法稍后介绍。
下例显示如何更改customer_id列为1的行:
first_name = "Jean"; |
该语句完成后,ID为1的顾客名被设置为"Jean"。
15.11 删除数据库的行
SQL DELETE语句用于删除表行。可以使用Statement类中定义的executeUpdate()方法或PreparedStatement类中定义的execute()方法。
下例显示如何删除customers表的顾客#5:
myStatement.executeUpdate( |
该语句完成后,customers表中顾客#5的行被删除。
15.12 处理数字
本节介绍在Java程序中存储数字的问题。Oracle数据库可以存储精度最大为38位的数字。在数字表示中,精度指的是在数字计算机内存中浮点数字所表示的精确度。数据库提供的38位精度允许存储非常大的数字。
在数据库中操作数字是非常方便的。但是,Java使用自己的类型集来表示数字,所以在程序中选择用于表示数字的Java类型时一定要慎重,尤其是这些数字存储在数据库中时。
在Java程序中存储整数时,可以使用short、int、long或java.math.BigInteger类型,这取决于想要存储的整数大小。表15-4显示了用于存储short、int和long类型的位数,以及每种类型支持的最小值和最大值。
表15-4 short、int和long类型
类 型 | 位 数 | 最 小 值 | 最 大 值 |
short | 16 | –32768 | 32767 |
int | 32 | –2147483648 | 2147483647 |
long | 64 | –9223372036854775808 | 9223372036854775807 |
在Java程序中存储浮点数,可以使用float、double或java.math.BigDecimal类型。表15-5显示了float和double类型,除包括表15-4显示的那些列外,表15-5还显示了每种类型支持的精度。
表15-5 float和double类型
类 型 | 位 数 | 最 小 值 | 最 大 值 | 精 度 |
float | 32 | –3.4E+38 | 3.4E+38 | 6位 |
double | 64 | –1.7E+308 | 1.7E+308 | 15位 |
可以看到,float可用于存储最大精度为6位的浮点数,double可用于存储最大精度为15位的浮点数。如果在Java程序中需要存储精度大于15位的浮点数,可以使用java.math. BigDecimal类型,它可以存储任意长度的浮点数。
除这些类型之外,还可以使用Oracle JDBC扩展类型之一来存储整型数或浮点数。这个类型就是oracle.sql.NUMBER,它允许存储最大精度为38位的数字。稍后本章会介绍oracle.sql.NUMBER类型。在Oracle Database 10g和更高的版本中,可以使用oracle.sql.BINARY_FLOAT和oracle.sql.BINARY_DOUBLE类型。这些类型允许存储BINARY_FLOAT和BINARY_DOUBLE数字。
下面看一些使用这些整型和浮点类型的例子,使用它们存储从products表中检索到的一行的product_id和price列值。假设ResultSet对象productResultSet已经用products表的一行的product_id和price列填充。product_id列定义为数据库INTEGER,price列定义为数据库NUMBER。下例创建了各种整型和浮点类型的变量,并把检索到的product_id和price列值存储到这些变量中:
short productIdShort = productResultSet.getShort("product_id");int productIdInt = productResultSet.getInt("product_id");long productIdLong = productResultSet.getLong("product_id");float priceFloat = productResultSet.getFloat("price");double priceDouble = productResultSet.getDouble("price");java.math.BigDecimal priceBigDec = productResultSet.getBigDecimal("price"); |
注意不同get方法的用法,它们将列值检索为不同类型,然后将输出存储在合适类型的Java变量中。
15.13 处理数据库Null值
数据库表的列可以定义为NULL或NOT NULL。NULL表示列可以存储NULL值;NOT NULL表示列不可以包含NULL值。NULL值表示值是未知的。当在数据库中创建表时,如果没有指定列是NULL还是NOT NULL,数据库默认为NULL。
Java对象类型,比如String,可用于存储数据库NULL值。当一个查询将包含NULL值的列检索存储到Java String时,String就会包含Java null值。例如,顾客 #5的phone列(定义为VARCHAR2)为NULL,下面的语句使用getString()方法将该值读取到String类型的phone中:
phone = customerResultSet.getString("phone"); |
运行该语句之后,phone Java String就包含了Java null值。
NULL值存储在Java对象中是非常合适的,但对于Java数字、逻辑和位类型类型呢?如果将NULL值检索存储到Java数据、逻辑或位变量中,例如int、float、boolean或byte,则变量将包含值零。对于数据库,零和NULL是不同的值:零是一个确定的值,NULL表示值未知。这就导致了一个问题,在Java程序中如何区分零和NULL。
有两种方法可以解决这个问题:
● 使用ResultSet中的wasNull()方法。当从数据库中检索到的值是NULL时,wasNull()方法返回true;否则,返回false。
● 可以使用Java包装类。包装类是一个Java类,允许定义包装对象,包装对象可用于存储数据库返回的列值。包装对象将数据库NULL值存储为Java null值,将非NULL值存储为正常值。
下例显示第一个方法的用法,使用了products表的产品#12。该行的product_type_id列为NULL值,而且该列定义为数据库INTEGER。并假设ResultSet对象productResultSet已经用products表的产品#12的product_id和product_type_id列填充。下例使用wasNull()方法检查读取的product_type_id列值是否为NULL:
System.out.println("product_type_id = " + |
因为product_type_id列包含NULL值,wasNull()返回true,所以会显示字符串Last value read was NULL。
第二种方法使用Java包装类,在看第二种方法的例子之前,需要解释包装类到底是什么。包装类定义在java.lang包中,下面的7个包装类就定义在该包中:
● java.lang.Short
● java.lang.Integer
● java.lang.Long
● java.lang.Float
● java.lang.Double
● java.lang.Boolean
● java.lang.Byte
使用这些包装类声明的对象可用于表示各种类型数字和Boolean类型的数据库NULL值。当数据库NULL被检索到这样一个对象时,它就会包含Java null值。下例声明了一个java.lang.Integer,名为productTypeId:
java.lang.Integer productTypeId |
然后,可以调用getObject()方法,将数据库NULL存储到productTypeId中,如下所示:
productTypeId = |
getObject()方法返回java.lang.Object类的一个实例,必须强制转换为合适的类型,本例中,转换为java.lang.Integer。假定此例从productResultSet中读取和上一个例子相同的行,getObject()会返回Java null值,而且该值会被复制到productTypeId中。当然,如果从数据库检索到的值不是NULL,productTypeId就会包含该值。例如,如果从数据库检索到的值是1,productTypeId就会包含值1。
也可以在JDBC语句中使用包装类对象,该语句执行INSERT或UPDATE,将列设置为正常值或NULL值。如果想要使用包装类对象将列值设置为NULL,就要将包装类对象设置为null,并在INSERT或UPDATE语句中使用它将数据库列设置为NULL。下例使用被设置为null的java.lang.Double对象,将产品#12的price列设置为NULL:
java.lang.Double price = null; |
15.14 控制数据库事务
第8章已经介绍了数据库事务和如何使用SQL COMMIT语句持久记录对表内容所作的更改,还介绍了如何使用ROLLBACK语句取消数据库事务中的更改。相同的概念适用于Java程序中使用JDBC语句执行的SQL语句。
默认情况下,使用JDBC执行的INSERT、UPDATE和DELETE语句立即被提交。这称为自动提交模式。一般来说,使用自动提交模式不是提交更改的首选方法,因为它与将事务当作逻辑工作单元的概念是对立的。使用自动提交模式,所有语句被当作单个事务,这通常是个错误的假设。而且,自动提交模式可能导致SQL语句的执行时间增加,这是由于每个语句通常都要提交。
幸运的是,可以使用Connection类的setAutoCommit()方法,为它传递Boolean值true或false,来开启或关闭自动提交模式。下例关闭Connection对象myConnection的自动提交模式:
myConnection.setAutoCommit(false); |
提示:
应该关闭自动提交模式,这一般会加快程序的运行速度。
关闭自动提交模式之后,可以使用Connection类的commit()方法提交事务更改,或者使用rollback()方法取消更改。下例中,commit()方法用于提交使用myConnection对象对数据库所作的更改:
myConnection.commit(); |
下例中,rollback()方法用于取消对数据库所作的更改:
myConnection.rollback(); |
如果已经关闭了自动提交模式,关闭Connection对象会执行隐式提交。因此,任何已经执行但还没有提交的DML语句都会被自动提交。
15.15 执行DDL语句
SQL数据定义语言(Data Definition Language,DDL)用于创建数据库用户、表和许多其他类型的组成数据库的数据库结构。DDL由CREATE、ALTER、DROP、TRUNCATE和RENAME等语句组成。可以使用Statement类的execute()方法在JDBC中执行DDL语句。下例中,CREATE TABLE语句用于创建表addresses,它用于存储顾客地址:
myStatement.execute( |
注意:
执行DDL语句会导致隐式提交。因此,如果在执行DDL语句之前执行了没有提交的DML语句,这些DML语句也会被提交。
15.16 处理异常
当数据库或JDBC驱动程序发生错误时,会触发java.sql.SQLException异常。java.sql.SQL Exception类是java.lang.Exception类的子类。因此,必须将所有的JDBC语句放入一个try/catch语句中,否则就会抛出java.sql.SQLException。当这样的异常发生时,Java就会查找合适的处理程序处理该异常。
如果catch子句包含java.sql.SQLException的处理程序,当数据库或JDBC驱动程序发生错误时,Java将移动到该处理程序,运行catch子句包含的合适代码。在处理程序代码中,可以显示错误代码和错误信息,从而帮助开发人员确定发生了什么。
下面的try/catch语句包含java.sql.SQLException类型异常的处理程序,该异常可能发生在try语句中:
try { |
注意:
因为假设java.sql.*已经导入,所以在catch中只使用SQLException,而不需要引用java.sql.SQLException。
try语句包含的JDBC语句可能导致抛出SQLException,catch子句包含错误处理代码。
SQLException类定义了4种方法,用于找出异常发生的原因:
● getErrorCode() 当错误发生在数据库或JDBC驱动程序时,该方法返回Oracle错误代码,它是一个5位数字。
● getMessage() 当错误发生在数据库时,该方法返回错误信息和5位Oracle错误代码。当错误发生在JDBC驱动程序时,该方法只返回错误信息。
● getSQLState() 当错误发生在数据库时,该方法返回包含SQL状态的5位代码。当错误发生在JDBC驱动程序时,该方法不返回任何值。
● printStackTrace() 异常发生时,该方法显示堆栈的内容。该信息可以进一步帮助找到错误所在。
下面的try/catch语句显示了这4种方法的用法:
try { |
如果代码抛出SQLException,而不是如上例所示进行本地处理,Java会在调用过程或函数中搜索合适的处理程序,直到找到一个合适的处理程序。如果没有找到合适的处理程序,异常会由默认异常处理程序处理,它显示Oracle错误代码、错误信息和堆栈跟踪。
15.17 关闭JDBC对象
在本章的示例中,已经创建了许多JDBC对象:一个Connection对象myConnection,一个Statement对象myStatement,两个ResultSet对象customerResultSet和productResultSet。当ResultSet对象不再有用时,应使用close()方法将它关闭。同样地,当Statement和Connection对象不再有用时,也应将其关闭。
下例中,使用close()方法关闭myStatement和myConnection对象:
myStatement.close(); |
通常在finally子句中关闭Statement和Connection对象。不管控制如何跳出try语句,包含在finally子句中的任何代码一定会运行。如果想要添加finally子句来关闭Statement和Connection对象,应在用于捕获异常的第一个try/catch语句之前声明这些对象。下例显示了如何构造main()方法,从而在finally子句中关闭Statement和Connection对象:
public static void main (String args []) { try { // connect to the database as store // create a Statement object // more of your code goes here // close the Connection object using the close() method |
注意,在使用close()方法关闭Statement和Connection对象之前,finally子句的代码检查它们是否不等于null。如果它们等于null,就没必要关闭它们。因为finally子句的代码都是最后运行且一定会运行的代码,所以Statement和Connection对象总是被关闭,而不管程序其他代码如何。为了简洁,只有本章的第一个程序使用了finally子句关闭Statement和Connection对象。
前面学习了如何编写JDBC语句连接数据库、运行DML和DDL语句、控制事务、处理异常和关闭JDBC对象。下一节将介绍一个完整的程序,示范JDBC的用法。
15.18 示例程序:BasicExample1.java
BasicExample1.java程序举例说明了本章已经介绍的这些概念。该程序和本章的其他程序可以在解压本书Zip文件后的Java文件夹中找到。所有程序都包含可帮助了解程序的详细注释。
/* // import the JDBC packages public class BasicExample1 { try { // EDIT AS NECESSARY TO CONNECT TO YOUR DATABASE // disable auto-commit mode // create a Statement object // create variables and objects used to represent // perform SQL INSERT statement to add a new row to the // perform SQL UPDATE statement to modify the first_name // perform SQL DELETE statement to remove customer #5 // create a ResultSet object, and populate it with the // loop through the rows in the ResultSet object using the System.out.println("customerId = " + customerId); // close the ResultSet object using the close() method // roll back the changes made to the database // create numeric variables to store the product_id and price columns // create another ResultSet object and retrieve the while (productResultSet.next()) { // check if the value just read by the get method was NULL // use the getObject() method to read the value, and convert it // retrieve the product_id and price column values into // close the ResultSet object // perform SQL DDL CREATE TABLE statement to create a new table // drop this table using the SQL DDL DROP TABLE statement // close the Connection object using the close() method |
注意:
需要使用正确的设置编辑标有文本"EDIT IF NECESSARY"的代码行,以访问数据库。
15.18.1 编译BasicExample1
要编译BasicExample1.java,在操作系统命令提示符下输入下列命令:
javac BasicExample1.java |
如果没有正确设置CLASSPATH环境变量,当试图编译FirstExample.java程序时,会得到下列错误信息:
FirstExample.java:22: cannot resolve symbol 1 error |
必须检查CLASSPATH环境变量的设置--CLASSPATH可能会漏掉Oracle JDBC类的Zip文件(例如,ojdbc6.jar)。参考15.2.4小节"设置CLASSPATH环境变量"。
提示:
可以在Java编译器上输入javac -help获取帮助。
15.18.2 运行BasicExample1
编译BasicExample1.java之后,输入下列命令,就可以运行结果可执行类文件BasicExample1. class:
java BasicExample1 |
警告:
Java是大小写敏感的,所以输入BasicExample1时一定要用大写字母B和E。
如果程序失败,出现下列的错误代码和信息,就表明数据库中不存在密码为store_password的store用户:
Error code = 1017 |
如果出现该错误,则检查store用户是否在数据库中。
程序还可能找不到数据库,这种情况下会出现下列错误:
Error code = 17002 |
一般地,出现该错误有两个原因:
● Oracle SID为ORCL的localhost计算机上没有数据库在运行。
● Oracle Net没有运行,或者没有监听端口1521上的连接。
应该确保程序中有正确的连接字符串,也要确保数据库和Oracle Net在运行。
假设程序运行,会得到下列输出:
Added row to customers table |
15.19 预备SQL语句
向数据库发送SQL语句时,数据库软件读取SQL语句,并检验它是否正确。这称为解析SQL语句。然后数据库软件建立一个称为执行计划的计划来实际运行语句。到目前为止,通过JDBC发送到数据库的所有SQL语句都需要建立一个新的执行计划。这是因为发送到数据库的每个SQL语句是不同的。
假设有一个Java应用程序重复执行相同的INSERT语句--一个例子就是向示例store中加载许多新产品,这个过程需要使用INSERT语句向products表添加许多行。看一个实际操作的Java语句。假设类Product定义如下:
class Product { |
下列代码创建了一个包含5个Product对象的数组。因为products表已经包含product_id值从1~12的行,新Product对象的productId属性从13开始:
Product [] productArray = new Product[5]; |
要向products表添加行,使用一个for循环,它包含一个执行INSERT语句的JDBC语句,列值来自productArray:
Statement myStatement = myConnection.createStatement(); |
每次循环迭代都会导致一个INSERT语句被发送到数据库中。因为表示每个INSERT语句的字符串包含不同的值,所以每次实际发送到数据库的INSERT都稍有不同。这表明数据库为每个INSERT语句创建了一个不同的执行计划--效率非常低。
令人高兴的是,JDBC提供了一个运行这样的SQL语句的更好方法。不使用JDBC Statement对象运行SQL语句,而是使用JDBC PreparedStatement对象。PreparedStatement对象允许执行相同的SQL语句,但为该语句的实际执行提供不同的值。这样效率更高,因为当SQL语句运行时,数据库使用相同的执行计划。下例创建了一个PreparedStatement对象,它包含与上一个循环相似的INSERT语句:
PreparedStatement myPrepStatement = myConnection.prepareStatement( |
在本例中,必须注意两点:
● prepareStatement()方法用于指定SQL语句。
● 问号字符(?)用于指定SQL语句实际运行时所提供的要使用变量的位置。
问号的位置非常重要:通过它们的位置引用它们,使用数字1引用第一个问号,使用2引用第二个,以此类推。
为预备语句提供Java变量的过程称为为语句绑定变量,变量本身称为绑定变量(bind variable)。要实际地为预备SQL语句提供变量,必须使用set方法。除了set方法用于提供变量值而不是读取值之外,这些方法与前面讨论的关于结果集的get方法相似。
例如,要将Java int变量intVar绑定到前面创建的PreparedStatement对象的product_id列,使用setInt(1, intVar)。第一个参数表明问号在前面prepareStatement()方法调用中指定的字符串中的数字位置。例如,值1对应第一个问号,它为INSERT语句中的product_id列提供一个值。同样地,要将Java String变量stringVar绑定到name列,使用setString(3, stringVar),因为第三个问号对应name列。在PreparedStatement对象中可以调用的其他方法有setFloat()和setDouble(),分别用于设置单精度浮点数和双精度浮点数。
下例使用一个循环显示了set方法的用法,将productArray的Product对象的属性绑定到PreparedStatement对象。注意,execute()方法用于实际运行SQL语句:
for (int counter = 0; counter < productArray.length; counter ++) { |
代码运行完成之后,products表就包含5个新行。
要使用PreparedStatement对象将数据库列设置为NULL,可以使用setNull()方法。例如,下列语句将description列设置为NULL:
myPrepStatement.setNull(4, java.sql.Types.VARCHAR); |
setNull()调用中的第一个参数是想要设置为NULL的列的数字位置。第二个参数是一个int,它对应想要设置为NULL的列的数据库类型。第二个参数必须使用java.sql.Types类中定义的常量指定。对于VARCHAR2列(description列定义为VARCHAR2),应使用java.sql.Types.VARCHAR。
15.20 示例程序:BasicExample2.java
示例BasicExample2.java的下列程序清单包含上一节所示的语句:
/* // import the JDBC packages class Product { public class BasicExample2 { // EDIT AS NECESSARY TO CONNECT TO YOUR DATABASE // disable auto-commit mode Product [] productArray = new Product[5]; // create a PreparedStatement object // initialize the values for the new rows using the // close the PreparedStatement object // retrieve the product_id, product_type_id, name, description, and // display the column values // close the ResultSet object using the close() method // roll back the changes made to the database // close the other JDBC objects } catch (SQLException e) { |
程序的输出如下:
product_id = 13 |
15.21 Oracle JDBC扩展
Oracle对JDBC的扩展允许访问Oracle提供的所有数据类型和Oracle特有的性能扩展。本节学习处理字符串、数字、日期和行标识符。可以阅读我编写的另一本书Oracle9i JDBC Programming来了解所有的Oracle类型和性能增强。
Oracle公司提供了两个JDBC扩展包:
● oracle.sql 包含支持所有Oracle数据库类型的类;
● oracle.jdbc 包含支持访问Oracle数据库的接口。
要将Oracle JDBC包导入Java程序中,可以向程序添加下列import语句:
import oracle.sql.*;import oracle.jdbc.*; |
当然,不需要导入所有包,只需导入程序实际使用的类和接口。下面介绍oracle.sql和oracle.jdbc包的主要特性。
15.21.1 oracle.sql包
oracle.sql包包含支持所有Oracle数据库类型的类。使用该包中定义的类的对象存储数据库值比使用正常Java对象效率要高。这是因为,首先,数据库值不需要转换为合适的基本Java类型。其次,使用Java float或double表示浮点数可能导致数字精度的损失。而如果使用oracle.sql.NUMBER对象,数字绝不会损失精度。
提示:
如果编写的程序要移动数据库的许多数据,应该使用oracle.sql.*类。
oracle.sql类扩展了oracle.sql.Datum类,它包含了所有类通用的功能。表15-6显示了oracle.sql类的子集和兼容的Oracle数据库类型的映射。
表15-6 类和兼容的Oracle数据库类型
类 | 兼容的数据库类型 |
oracle.sql.NUMBER | INTEGER NUMBER |
oracle.sql.CHAR
| CHAR VARCHAR2 NCHAR NVARCHAR2 |
oracle.sql.DATE | DATE |
oracle.sql.BINARY_FLOAT | BINARY_FLOAT |
oracle.sql.BINARY_DOUBLE | BINARY_DOUBLE |
oracle.sql.ROWID | ROWID |
从表15-6可以看出,oracle.sql.NUMBER对象与INTEGER或NUMBER数据库类型兼容。oracle.sql.CHAR对象与CHAR、VARCHAR2、NCHAR和NVARCHAR2数据库类型兼容(NCHAR和NVARCHAR2数据库类型一般用于存储非英语字符)。
使用oracle.sql类声明的对象将数据存储为字节数组(也称为SQL格式),而且不重新格式化从数据库检索到的数据。这就是说,当在SQL格式对象与数据库中存储的值之间进行转换时,并不会丢失任何信息。
每个oracle.sql对象都有一个getBytes()方法,它返回此对象中存储的二进制数据。每个oracle.sql对象也都有一个toJdbc()方法,它将二进制数据以兼容的Java类型返回(唯一的例外是oracle.sql.ROWID,它的toJdbc()始终返回oracle.sql.ROWID)。
每个oracle.sql类还提供将SQL格式的二进制数据转换为核心Java类型的方法。例如,stringValue()返回Java String值,intValue()返回Java int,floatValue()返回float,doubleValue()返回double,bigDecimalValue()返回java.math.BigDecimal,dateValue()返回java.sql.Date,等等。当想要将SQL格式数据存储在核心Java类型或将SQL数据输出到屏幕时,使用这些方法。每个oracle.sql类也包含一个构造函数,构造函数将Jave变量、对象或字节数组作为输入。
稍后介绍oracle.jdbc.OraclePreparedStatement类,它包含许多set方法,这些set方法可以指定oracle.sql对象的值。OracleResultSet类定义了许多get方法,可用于从oracle.sql对象读取值。
下面介绍主要的oracle.sql类。
1. oracle.sql.NUMBER类
oracle.sql.NUMBER类与数据库INTEGER和NUMBER类型兼容,可用于表示最大精度为38位的数字。下例创建了一个oracle.sql.NUMBER对象customerId,使用构造函数将其设置为值6:
oracle.sql.NUMBER customerId = new oracle.sql.NUMBER(6); |
可以使用intValue()方法读取customerId存储的值,并返回为Java int类型。例如:
int customerIdInt = customerId.intValue(); |
也可以将oracle.sql.NUMBER对象设置为浮点数。下一个例子将值19.95传递给oracle.sql.NUMBER对象price的构造函数:
oracle.sql.NUMBER price = new oracle.sql.NUMBER(19.95); |
可以使用floatValue()、doubleValue()和bigDecimalValue()方法读取price存储的浮点数,分别返回Java float、double和bigDecimal。也可以使用intValue()获得截断为int的浮点数。这样19.95将返回为19。下例显示了这些方法的用法:
float priceFloat = price.floatValue();double priceDouble = price.doubleValue();java.math.BigDecimal priceBigDec = price.bigDecimalValue();int priceInt = price.intValue();stringValue()方法返回Java String值。String priceString = price.stringValue(); |
2. oracle.sql.CHAR类
oracle.sql.CHAR类与数据库的CHAR、VARCHAR2、NCHAR和NVARCHAR2类型兼容。Oracle数据库和oracle.sql.CHAR类都包含对许多不同语言的全球化支持。关于Oracle支持的各种语言的详细信息,参考Oracle公司出版的Oracle Database Globalization Support Guide。
将从数据库检索到的字符数据存储到oracle.sql.CHAR对象时,Oracle JDBC驱动程序使用默认的数据库字符集WE8ISO8859P1(ISO 8859-1西欧)或UTF8(Unicode 3.0 UTF-8通用)返回该对象。
当将oracle.sql.CHAR对象传递到数据库时,对对象使用的字符集有所限制,具体使用的字符集取决于存储对象的数据库列类型。如果将oracle.sql.CHAR对象存储在CHAR或VARCHAR2列中,必须使用US7ASCII(ASCII 7位美国)、WE8ISO8859P1(ISO 8859-1 西欧)或UTF8(Unicode 3.0 UTF-8 通用)。如果将oracle.sql.CHAR对象存储在NCHAR或NVARCHAR2列中,必须使用数据库使用的字符集。
创建oracle.sql.CHAR对象时,必须遵照下列两个步骤:
(1) 创建一个oracle.sql.CharacterSet对象,包含想要使用的字符集。
(2) 使用oracle.sql.CharacterSet对象创建一个oracle.sql.CHAR对象。
下面详细介绍这些步骤。
步骤1:创建一个oracle.sql.CharacterSet对象 下例创建了一个oracle.sql.CharacterSet对象myCharSet:
oracle.sql.CharacterSet myCharSet =CharacterSet.make(CharacterSet.US7ASCII_CHARSET); |
make()方法接受一个int,指定要使用的字符集。本例中,常量US7ASCII_CHARSET(定义在oracle.sql.CharacterSet类中)用于指定要使用的US7ASCII字符集。其他int值包括UTF8_CHARSET(指定UTF8)和DEFAULT_CHARSET(指定数据库使用的字符集)。
步骤2:创建一个oracle.sql.CHAR对象 下例使用上一步创建的myCharSet对象来创建一个oracle.sql.CHAR对象firstName:
oracle.sql.CHAR firstName = new oracle.sql.CHAR("Jason", myCharSet); |
用字符串Jason填充firstName对象。可以使用stringValue()方法读取这一字符串。例如:
String firstNameString = firstName.stringValue();System.out.println("firstNameString = " + firstNameString); |
显示出firstNameString = Jason。
同样地,下例创建了另一个oracle.sql.CHAR对象lastName:
oracle.sql.CHAR lastName = new oracle.sql.CHAR("Price", myCharSet); |
还可以直接显示oracle.sql.CHAR对象的值,如下所示:
System.out.println("lastName = " + lastName); |
该语句将显示:
lastName = Price |
3. oracle.sql.DATE类
oracle.sql.DATE类兼容数据库DATE类型。下例创建了一个oracle.sql.DATE对象dob:
oracle.sql.DATE dob = new oracle.sql.DATE("1969-02-22 13:54:12"); |
注意,构造函数接受一个字符串,格式为YYYY-MM-DD HH:MI:SS,其中YYYY是年,MM是月,DD是日,HH是小时,MI是分钟,SS是秒。可以使用stringValue()方法将dob存储的值读取为Java String,如下所示:
String dobString = dob.stringValue(); |
本例中,dobString将包含2/22/1969 13:54:12(当使用Java String时,格式变为MM/DD/YYYY HH:MI:SS)。
还可以将java.sql.Date对象传递给oracle.sql.DATE构造函数,如下所示:
oracle.sql.DATE anotherDob = |
这样,anotherDob将包含oracle.sql.DATE 1969-02-22 00:00:00。
4. oracle.sql.ROWID类
oracle.sql.ROWID类兼容数据库ROWID类型。ROWID包含数据库中的行的物理地址。下例创建了一个oracle.sql.ROWID对象rowid:
oracle.sql.ROWID rowid; |
15.21.2 oracle.jdbc包
使用oracle.sql对象,oracle.jdbc包的类和接口允许读取和写入数据库中的列值。oracle.jdbc包还包含许多性能增强。本节介绍oracle.jdbc包的内容和如何创建customers表的一行。然后介绍如何使用oracle.sql对象读取该行。
1. oracle.jdbc包的类和接口
表15-7描述了oracle.jdbc包的类和接口。
表15-7 oracle.jdbc包的类和接口
名 称 | 类 或接 口 | 描 述 |
OracleDriver
| 类 | 实现java.sql.Driver。使用java.sql.DriverManager类的registerDriver()方法在程序中注册Oracle JDBC驱动程序时,输入该类的一个对象 |
OracleConnection
| 接口 | 实现java.sql.Connection。该接口扩展了标准JDBC连接功能以使用OracleStatement对象。该接口的性能比标准JDBC函数的性能有提高 |
OracleStatement
| 接口 | 实现java.sql.Statement,是OraclePreparedStatement 和OracleCallableStatement类的超类 |
OraclePreparedStatement
| 接口 | 实现java.sql.PreparedStatement,是OracleCallableStatement的超类。该接口支持用于绑定oracle.sql对象的各种set方法 |
OracleCallableStatement
| 接口 | 实现java.sql.CallableStatement。该接口包含用于绑定oracle.sql对象的各种get和set方法 |
OracleResultSet
| 接口 | 实现java.sql.ResultSet。该接口包含用于绑定oracle.sql对象的各种get方法 |
OracleResultSetMetaData
| 接口 | 实现java.sql.ResultSetMetaData。该接口包含一些方法,这些方法用于检索Oracle结果集的元数据,比如列名和列类型 |
OracleDatabaseMetaData
| 类 | 实现java.sql.DatabaseMetaData。该类包含一些方法,这些方法用于检索Oracle数据库的元数据,比如数据库软件版本 |
OracleTypes
| 类 | 定义数据库类型的整数常量。该类复制标准java.sql.Types类,还额外包含所有Oracle类型的整数常量 |
2. 使用OraclePreparedStatement对象
OraclePreparedStatement接口实现java.sql.PreparedStatement。该接口支持用于绑定oracle.sql对象的各种set方法。
上一节介绍了下面几个oracle.sql对象:
● 一个oracle.sql.NUMBER对象customerId,设置为6
● 一个oracle.sql.CHAR对象firstName,设置为Jason
● 另一个oracle.sql.CHAR对象lastName,设置为Price
● 一个oracle.sql.DATE对象dob,设置为1969-02-22 13:54:12
要在SQL DML语句中直接使用这些对象,必须使用OraclePreparedStatement对象,它包含可以处理oracle.sql对象的set方法。下例创建了一个OraclePreparedStatement对象myPrepStatement,稍后用它向customers表添加一行:
OraclePreparedStatement myPrepStatement = |
注意prepareStatement()方法返回的JDBC PreparedStatement对象强制转换为OraclePreparedStatement对象,并存储在myPrepStatement中。
下一步使用set方法将oracle.sql对象绑定到myPrepStatement。这需要为myPrepStatement中由问号字符标记的占位符赋值。同使用诸如setInt()、setFloat()、setString()和setDate())等set方法将Java变量绑定到PreparedStatement对象一样,也可以使用set方法将oracle.sql对象绑定到OraclePreparedStatement对象,比如使用setNUMBER()、setCHAR()和setDATE()。
下例显示了如何使用合适的set方法将customerId、firstName、lastName和dob对象绑定到myPrepStatement:
myPrepStatement.setNUMBER(1, customerId); |
下例将myPrepStatement中的第五个问号设置为NULL(第五个问号对应于customers表中的phone列):
myPrepStatement.setNull(5, OracleTypes.CHAR); |
int常量OracleTypes.CHAR用于指定数据库列类型兼容oracle.sql.CHAR类型。之所以使用OracleTypes.CHAR是因为phone列定义为数据库VARCHAR2。
最后使用execute()方法运行INSERT语句:
myPrepStatement.execute(); |
这样就向customers表添加了一行。
3. 使用OracleResultSet对象
OracleResultSet接口实现java.sql.ResultSet,而且包含可以处理oracle.sql对象的get方法。下面介绍如何使用OracleResultSet对象检索前面已经添加到customers表中的行。
首先需要一个JDBC Statement对象,通过它SQL语句才可以运行:
Statement myStatement = myConnection.createStatement(); |
接下来,下例创建了一个OracleResultSet对象customerResultSet,使用顾客#6的ROWID、customer_id、first_name、last_dob和phone列填充它:
OracleResultSet customerResultSet = |
已经定义了5个oracle.sql对象:rowid、customerId、firstName、lastName和dob。这些对象可用于存储前5个列值。为了存储phone列,需要一个oracle.sql.CHAR对象:
oracle.sql.CHAR phone = new oracle.sql.CHAR("", myCharSet); |
一个OracleResultSet对象包含许多get方法,返回各种oracle.sql对象。getCHAR()用于获得oracle.sql.CHAR,getNUMBER()用于获得oracle.sql.NUMBER,getDATE()用于获得oracle.sql. DATE,依此类推。
下面这个while循环调用合适的get方法将值从customerResultSet复制到rowid、customerId、firstName、lastName、dob和phone:
while (customerResultSet.next()) { System.out.println("rowid = " + rowid.stringValue()); |
为了显示这些值,此示例调用stringValue()方法将rowid、customerId和dob对象转换为Java String值。对于firstName、lastName和phone对象,在System.out.println()调用中直接使用了这些对象。
下一节展示了一个完整的程序,其中包含前面几节已展示的那些语句。
下面的程序BasicExample3.java包含前面几节已展示的那些语句的:
/ * / /导入JDBC包 / /导入Oracle JDBC扩展包的 公共类BasicExample3 { / /根据需要进行编辑,连接到数据库 / /禁用auto-commit模式 / /创建一个oracle.sql.NUMBER对象 / /创建两个oracle.sql.CHAR的对象 / /创建一个对象oracle.sql.DATE / /创建一个OraclePreparedStatement的对象 / /使用 / /设置手机列到:NULL / /运行PreparedStatement / /检索的ROWID,CUSTOMER_ID,FIRST_NAME,姓氏,出生日期, / /声明一个oracle.sql.ROWID对象存储ROWID, / /显示列值的行使用 (ROWID =“+ rowid.stringValue()) / /关闭OracleResultSet的对象使用的close()方法 / /回滚所做的更改到的数据库 / /关闭其他JDBC对象 }赶上(SQLException异常E){ |
程序的输出如下:
customerIdInt = 6 |
下面的程序BasicExample3.java包含前面几节已展示的那些语句的:
/ * / /导入JDBC包 / /导入Oracle JDBC扩展包的 公共类BasicExample3 { / /根据需要进行编辑,连接到数据库 / /禁用auto-commit模式 / /创建一个oracle.sql.NUMBER对象 / /创建两个oracle.sql.CHAR的对象 / /创建一个对象oracle.sql.DATE / /创建一个OraclePreparedStatement的对象 / /使用 / /设置手机列到:NULL / /运行PreparedStatement / /检索的ROWID,CUSTOMER_ID,FIRST_NAME,姓氏,出生日期, / /声明一个oracle.sql.ROWID对象存储ROWID, / /显示列值的行使用 (ROWID =“+ rowid.stringValue()) / /关闭OracleResultSet的对象使用的close()方法 / /回滚所做的更改到的数据库 / /关闭其他JDBC对象 }赶上(SQLException异常E){ |
程序的输出如下:
customerIdInt = 6 |
本章介绍了以下内容:
● JDBC API使Java程序可以访问数据库。
● Oracle JDBC驱动程序用于连接Oracle数据库。
● 可以使用JDBC执行SQL语句。
● Oracle开发了许多对标准JDBC的扩展,允许获得对所有Oracle数据库类型的访问。
下一章介绍如何优化SQL语句,使系统获得最大性能。
下面的程序BasicExample3.java包含前面几节已展示的那些语句的:
/ * / /导入JDBC包 / /导入Oracle JDBC扩展包的 公共类BasicExample3 { / /根据需要进行编辑,连接到数据库 / /禁用auto-commit模式 / /创建一个oracle.sql.NUMBER对象 / /创建两个oracle.sql.CHAR的对象 / /创建一个对象oracle.sql.DATE / /创建一个OraclePreparedStatement的对象 / /使用 / /设置手机列到:NULL / /运行PreparedStatement / /检索的ROWID,CUSTOMER_ID,FIRST_NAME,姓氏,出生日期, / /声明一个oracle.sql.ROWID对象存储ROWID, / /显示列值的行使用 (ROWID =“+ rowid.stringValue()) / /关闭OracleResultSet的对象使用的close()方法 / /回滚所做的更改到的数据库 / /关闭其他JDBC对象 }赶上(SQLException异常E){ |
程序的输出如下:
customerIdInt = 6 |