JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,
可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
设置表
1.创建表
首先,我们在我们的示例数据库创 建其中一张表 COFFEES,包含在咖啡店所卖咖啡的必要的信息,包括咖啡名字,他们的价格,本星期卖了多少磅及迄今为止卖的数目。关于 COFFEES 表我们以后会详细描述,如下:
我们写了创建 COFFEES 表的 SQL 语句。现在我们在它外面加上引号(使它成为字符串),并且字符串赋值给变量createTableCoffees,在以后的 JDBC代码中我们可以使用此变量。正如看到的,DBMS 并不在意分行,但对 Java 语言来,String 对象分行是通不过编译的。因而,我们可以用加号 (+) 把每一行的串连接。
String createTableCoffees = "CREATE TABLE COFFEES " +
"(COF_NAME VARCHAR(32), SUP_ID INTEGER, PRICE FLOAT, " +
"SALES INTEGER, TOTAL INTEGER)";
我们在 CREATE TABLE 语句中使用的数据类型是通用的 SQL 类型(也称 JDBC 类型)它们在类java.sql.Types 中定义。DBMSs 通常使用这些标准的类型,因此,当你要尝试一些 JDBC应用程序时,你可以直接使用 CreateCoffees.java 应用程序,它使用了 CREATE TABLE 语句。如果你的 DBMS 使用了它的自己的本地的类型名字,我们为你供应其它的应用程序,我们将在后面详细解释。
在运用任何应用程序前,当然,我们将让你了解 JDBC 的基础。
2.创建对象
Statement 对象用于把 SQL 语句发送到 DBMS 。你只须简单地创建一个 Statement 对象并且然后执行它,使用适当的方法执行你发送的 SQL 语句。对 SELECT 语句来说,可以使用 executeQuery。要创建或修改表的语句,使用的方法是 executeUpdate。
需要一个活跃的连接的来创建 Statement 对象的实例。在下面的例子中,我们使用我们的 Connection 对象 con 创建 Statement 对象 stmt:
Statement stmt = con.createStatement();
到此 stmt 已经存在了,但它还没有把 SQL 语句传递到 DBMS。我们需要提供 SQL 语句作为参数提供给我们使用的 Statement 的方法。例如,在下面的代码段里,我们使用上面例子中的 SQL 语句作为 executeUpdate 的参数:
stmt.executeUpdate("CREATE TABLE COFFEES " +
"(COF_NAME VARCHAR(32), SUP_ID INTEGER, PRICE FLOAT, " +
"SALES INTEGER, TOTAL INTEGER)");
因为我们已经把 SQL 语句赋给了 createTableCoffees 变量,我们可以如下方式书写代码:
stmt.executeUpdate(createTableCoffees);
3.输入数据
我们已经显示了如何通过指定列名、数据类型来创建表 COFFEES,但是这仅仅建立表的结构。表还没有任何数据。我们将次输入一行数据到表中,提供每列的信息,注意插入的数据显示顺序跟表创建时候是一样的,既缺省顺序。
下列代码插入一个行数据,COF_NAME 的值为 Colombian,SUP_ID 为 101,PRICE 为 7.99,SALES 0,TOTAL 0。就象创建 COFFEES 表一样,我们创建一 Statement 对象,并执行 executeUpdate 方法。
因为 SQL 语句一行显示不下,因此我们把它分为两行,并用加号 (+) 相连。特别要注意的是,在 COFFEES 和 VALUES 之间要有空格。这个空格必须在引号之内并且要在 COFFEES 跟 VALUES 之间;没有这个空格,SQL 语句将被错误地被读作为 "INSERT INTO COFFEESVALUES ...",并且 DBMS 将寻找表 COFFEESVALUES。还要注意的是在 coffee name 上我们使用了单引号。
Statement stmt = con.createStatement();
stmt.executeUpdate(
"INSERT INTO COFFEES " +
"VALUES ('Colombian', 101, 7.99, 0, 0)");
下面的代码把第二行插入到表 COFFEES 中。我们可以在使用 Statement 对象而无须为每次执行创建一个新的。
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('French_Roast', 49, 8.99, 0, 0)");
剩下行的数据如下:
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('Espresso', 150, 9.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('Colombian_Decaf', 101, 8.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('French_Roast_Decaf', 49, 9.99, 0, 0)");
4.取得数据
既然表 COFFEES 中已经有数据了,我们就可以写一个 SELECT 语句来取得这些值。下面的 SQL 语句中星号 (*) 表示选择所有的列。因为没有用 WHERE 子句来限制所选的行,因此下面的 SQL 语句选择的是整个表。
SELECT * FROM COFFEES
结果是整个表的数据,如下:
COF_NAME SUP_ID PRICE SALES TOTAL
--------------- ------ ----- ----- -----
Colombian 101 7.99 0 0
French_Roast 49 8.99 0 0
Espresso 150 9.99 0 0
Colombian_Decaf 101 8.99 0 0
French_Roast_Decaf 49 9.99 0 0
如果你直接在数据库系统里输入 SQL 查询语句,你将在你的终端上看到如上的结果。当我们通过一个 Java应用程序存取一个数据库时,正如我们马上要做的一样,我们需要检索结果以便我们能使用他们。你将在下一节看到如何实现。
这是 SELECT 语句的另一个例子,这将得到咖啡及其各自每磅单价的列表。
SELECT COF_NAME, PRICE FROM COFFEES
查询的结果集将具有如下形式:
COF_NAME PRICE
-------- ---------- -----
Colombian 7.99
French_Roast 8.99
Espresso 9.99
Colombian_Decaf 8.99
French_Roast_Decaf 9.99
上面 SELECT 语句取得了所有咖啡的名字及价格。而下面的 SELECT 语句限制那些每磅价格低于 .00 的咖啡才被选择。
SELECT COF_NAME, PRICE
FROM COFFEES
WHERE PRICE < 9.00
结果集将具有如下形式:
COF_NAME PRICE
-------- ------- -----
Colombian 7.99
French_Roast 8.99
Colombian Decaf 8.99
1.驱动程序
DriverManager类包含一列Driver类,它们已通过调用方法DriverManager.registerDriver对自己进行了注册。所有Driver类都必须包含有一个静态部分。它创建该类的实例,然后在加载该实例时DriverManager类进行注册。这样,用户正常情况下将不会直接调用DriverManager.registerDriver;而是在加载驱动程序时由驱动程序自动调用。加载Driver类,然后自动在DriverManager中注册的方式有两种:
(1)调用方法Class.forName
这将显式地加载驱动程序类。由于这与外部设置无关,因此推荐使用这种加载驱动程序的方法。以下代码加载类acme.db.Driver:Class.forName("acme.db.Driver")。
如果将acme.db.Driver编写为加载时创建实例,并调用以该实例为参数的DriverManager.registerDriver(本该如此),则它在DriverManager的驱动程序列表中,并可用于创建连接。
(2)将驱动程序添加到Java.lang.System的属性jdbc.drivers中
这是一个由DriverManager类加载的驱动程序类名的列表,由冒号分隔:初始化DriverManager类时,它搜索系统属性jdbc.drivers,如果用户已输入了一个或多个驱动程序,则DriverManager类将试图加载它们。以下代码说明程序员如何在~/.hotJava/properties中输入三个驱动程序类(启动时,HotJava将ivers=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");
3.发送语句
综述
Statement对象用于将SQL语句发送到数据库中。实际上有三种Statement对象,它们都作为在给定连接上执行SQL语句的包容器:Statement、PreparedStatement(它从Statement继承而来)和CallableStatement(它从PreparedStatement继承而来)。它们都专用于发送特定类型的SQL语句:Statement对象用于执行不带参数的简单SQL语句;PreparedStatement对象用于执行带或不带IN参数的预编译SQL语句;CallableStatement对象用于执行对数据库已存储过程的调用。 Statement接口提供了执行语句和获取结果的基本方法;PreparedStatement接口添加了处理IN参数的方法;而CallableStatement添加了处理OUT参数的方法。
建立了到特定数据库的连接之后,就可用该连接发送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");
5.使用执行语句
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的返回值总为零。
执行语句的所有方法都将关闭所调用的Statement对象的当前打开结果集(如果存在)。这意味着在重新执行Statement对象之前,需要完成对当前ResultSet对象的处理。应注意,继承了Statement接口中所有方法的PreparedStatement接口都有自己的executeQuery、executeUpdate和execute方法。Statement对象本身不包含SQL语句,因而必须给Statement.execute方法提供SQL语句作为参数。PreparedStatement对象并不需要SQL语句作为参数提供给这些方法,因为它们已经包含预编译SQL语句。
CallableStatement对象继承这些方法的PreparedStatement形式。对于这些方法的PreparedStatement或CallableStatement版本,使用查询参数将抛出SQLException。
6.语句完成
当连接处于自动提交模式时,其中所执行的语句在完成时将自动提交或还原。语句在已执行且所有结果返回时,即认为已完成。对于返回一个结果集的executeQuery方法,在检索完ResultSet对象的所有行时该语句完成。对于方法executeUpdate,当它执行时语句即完成。但在少数调用方法execute的情况中,在检索所有结果集或它生成的更新计数之后语句才完成。
有些DBMS将已存储过程中的每条语句视为独立的语句;而另外一些则将整个过程视为一个复合语句。在启用自动提交时,这种差别就变得非常重要,因为它影响什么时候调用commit方法。在前一种情况中,每条语句单独提交;在后一种情况中,所有语句同时提交。
7.关闭对象
Statement对象将由Java垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要Statement对象时显式地关闭它们。这将立即释放DBMS资源,有助于避免潜在的内存问题。
8.使用execute
execute方法应该仅在语句能返回多个ResultSet对象、多个更新计数或ResultSet对象与更新计数的组合时使用。当执行某个已存储过程或动态执行未知SQL字符串(即应用程序程序员在编译时未知)时,有可能出现多个结果的情况,尽管这种情况很少见。例如,用户可能执行一个已存储过程,并且该已存储过程可执行更新,然后执行选择,再进行更新,再进行选择,等等。通常使用已存储过程的人应知道它所返回的内容。
因为方法execute处理非常规情况,所以获取其结果需要一些特殊处理并不足为怪。例如,假定已知某个过程返回两个结果集,则在使用方法execute执行该过程后,必须调用方法getResultSet获得第一个结果集,然后调用适当的getXXX方法获取其中的值。要获得第二个结果集,需要先调用getMoreResults方法,然后再调用getResultSet方法。如果已知某个过程返回两个更新计数,则首先调用方法getUpdateCount,然后调用getMoreResults,并再次调用getUpdateCount。
对于不知道返回内容,则情况更为复杂。如果结果是ResultSet对象,则方法execute返回true;如果结果是Javaint,则返回false。如果返回int,则意味着结果是更新计数或执行的语句是DL命令。在调用方法execute之后要做的第一件事情是调用getResultSet或getUpdateCount。调用方法getResultSet可以获得两个或多个ResultSet对象中第一个对象;或调用方法getUpdateCount可以获得两>
当SQL语句的结果不是结果集时,则方法getResultSet将返回null。这可能意味着结果是一个更新计数或没有其它结果。在这种情况下,判断null真正含义的唯一方法是调用方法getUpdateCount,它将返回一个整数。这个整数为调用语句所影响的行数;如果为-1则表示结果是结果集或没有结果。如果方法getResultSet已返回null(表示结果不是ResultSet对象),则返回值-1表示没有其它结果。也就是说,当下列条件为真时表示没有结果(或没有其它结果):
((stmt.getResultSet()==null)&&(stmt.getUpdateCount()==-1))
如果已经调用方法getResultSet并处理了它返回的ResultSet对象,则有必要调用方法getMoreResults以确定是否有其它结果集或更新计数。如果getMoreResults返回true,则需要再次调用getResultSet来检索下一个结果集。如上所述,如果getResultSet返回null,则需要调用getUpdateCount来检查null是表示结果为更新计数还是表示没有其它结果。
当getMoreResults返回false时,它表示该SQL语句返回一个更新计数或没有其它结果。因此需要调用方法getUpdateCount来检查它是哪一种情况。在这种情况下,当下列条件为真时表示没有其它结果:
((stmt.getMoreResults()==false)&&(stmt.getUpdateCount()==-1))
9.组件框架
JavaSoft提供三种JDBC产品组件,它们是Java开发工具包(JDK)的组成部份:JDBC驱动程序管理器、JDBC驱动程序测试工具包和JDBC-ODBC桥。
JDBC驱动程序管理器是JDBC体系结构的支柱。它实际上很小,也很简单;其主要作用是把Java应用程序连接到正确的JDBC驱动程序上,然后即退出。
JDBC驱动程序测试工具包为使JDBC驱动程序运行您的程序提供一定的可信度。只有通过JDBC驱动程序测试的驱动程序才被认为是符合JDBC标准TM的。
JDBC-ODBC桥使ODBC驱动程序可被用作JDBC驱动程序。它的实现为JDBC的快速发展提供了一条途径,其长远目标提供一种访问某些不常见的DBMS(如果对这些不常见的DBMS未实现JDBC)的方法。
10.驱动类型
目前比较常见的JDBC驱动程序可分为以下四个种类:
(1)JDBC-ODBC桥加ODBC驱动程序
JavaSoft桥产品利用ODBC驱动程序提供JDBC访问。注意,必须将ODBC二进制代码(许多情况下还包括数据库客户机代码)加载到使用该驱动程序的每个客户机上。因此,这种类型的驱动程序最适合于企业网(这种网络上客户机的安装不是主要问题),或者是用Java编写的三层结构的应用程序服务器代码。
(2)本地API
这种类型的驱动程序把客户机API上的JDBC调用转换为Oracle、Sybase、Informix、DB2或其它DBMS的调用。注意,象桥驱动程序一样,这种类型的驱动程序要求将某些二进制代码加载到每台客户机上。
(3)JDBC网络纯Java驱动程序
这种驱动程序将JDBC转换为与DBMS无关的网络协议,之后这种协议又被某个服务器转换为一种DBMS协议。这种网络服务器中间件能够将它的纯Java客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的JDBC驱动程序。有可能所有这种解决方案的提供者都提供适合于Intranet用的产品。为了使这些产品也支持Internet访问,它们必须处理Web所提出的安全性、通过防火墙的访问等方面的额外要求。几家提供者正将JDBC驱动程序加到他们现有的数据库中间件产品中。
(4)本地协议纯Java驱动程序
这种类型的驱动程序将JDBC调用直接转换为DBMS所使用的网络协议。这将允许从客户机机器上直接调用DBMS服务器,是Intranet访问的一个很实用的解决方法。由于许多这样的协议都是专用的,因此数据库提供者自己将是主要来源,有几家提供者已在着手做这件事了。
获取驱动
目前已有几十个(1)类的驱动程序,即可与Javasoft桥联合使用的ODBC驱动程序的驱动程序。有大约十多个属于种类(2)的驱动程序是以DBMS的本地API为基础编写的。只有几个属于种类(3)的驱动程序,其首批提供者是SCO、OpenHorizon、Visigenic和WebLogic。此外,JavaSoft和数据库连接的领先提供者Intersolv还合作研制了JDBC-ODBC桥和JDBC驱动程序测试工具包。
尽管JDBC在JAVA语言层面实现了统一,但不同数据库仍旧有许多差异。为了更好地实现跨数据库操作,于是诞生了Hibernate项目,Hibernate是对JDBC的再封装,实现了对数据库操作更宽泛的统一和更好的可移植性。