________________________________________
概要
JDBC接口允许外部对SQL数据库进行管理以及对内容进行修改操作。通过使用第三方驱动,它们允许使用SQL语句调用。而且,Java内建的JDBC具有丰富的指令集合,使用起来相当的简单和直观。
举个例子:您正着手写一个Java程序,需要用到与数据库交互的操作。您得使用标准库提供的指令(方法)。首先建立一个数据库的连接。然后,您使用JDBC向数据库传送SQL语句,再处理返回的结果。最后关闭数据库的连接。
________________________________________
建立连接
我要从什么地方下手呢?当然了,要先在您的机器上安装Java、JDBC和DBMS(数据库)。由于我想使用Oracle数据,所以我需要找到指定的驱动。先到Oracle官方网址看看,真的有,下载地址: http://www.oracle.com/technology/global/cn/software/tech/java/sqlj_jdbc/index.html 。
正如先前所说,在对数据库操作(access)之前,必须在程序(client)与数据库(server)之间建立一个连接。
• 加载指定的第三方驱动
为何要来这一手呢?为使操作更轻便、代码复用程度更高,API常被尽可能地设计成与版本及厂商无关。由于不同的关系型数据库具有不同的形为,我们得告诉所用的驱动,这样它们才能做出正确的行为。
下面是加载Oracle驱动的代码:
Class.forName("oracle.jdbc.driver.OracleDriver")
• 建立连接
一旦驱动被加载并处于准备连接状态,您就可以使用下面的代码建立一个连接实例:
Connection con = DriverManager.getConnection(
"jdbc:oracle:thin:@dbaprod1: 1521:orcl", username, passwd);
好,让我们一起来弄懂这里用到的术语吧。第一个字符串(string)是一个数据库的URL,包括协议(jdbc), 厂商(oracle), 驱动(thin), 服务器(dbaprod1), 端口号 (1521), 和服务器实例(orcl) 。数据库连接的用户名和密码分别为: username、 passwd。
完了。最后一步返回的是一个打开的连接,用来传送SQL语句与数据库进行交互操作。在上面的代码中,con就是这样的一个连接,稍后我们会用到。(注:上面代码所示范的,很可能会与您的环境不一致,请依照您的环境作必要的修改)
________________________________________
建立JDBC语句(Statement)
JDBC语句用于向数据库传送SQL语句。JDBC语句不同于SQL语句,它是与数据库连接密切相关的,不单是一个SQL语句。您可以把JDBC想象成一个道往数据库的通道,用来传送一个或多个SQL语句到数据库中。
一个活动连接需要建立一个 Statement 对象。示范如下(用到先前建立的Connection 对象con):
Statement stmt = con.createStatement() ;
到此,一个Statement 对象已经存在,还差一个传送到数据库的SQL语句,下面就学到。
________________________________________
建立JDBC填充语句(PreparedStatement)
有时候,使用PreparedStatement对象向数据库传送SQL语句更加方便而且更有效率。有别于它的父类Statement,它只在创建的时候被赋予一个SQL语句。该条SQL语句被传送到数据库时,立刻被编译。因此,PreparedStatement语句直接绑定于通道和被编译的SQL语句上。
优点显而易见:如果您需要使用多个相同或相似的SQL语句(仅其中的参数不同),使用PreparedStatement只需要被编译一次并被优化。使用普通的Statement就需要多次并且是逐个地被编译。
PreparedStatements 同样使用Connection 的方法建立。下面的代码示范如何建立一个填充语句,并且带有三个参数。
PreparedStatement prepareUpdatePrice = con.prepareStatement(
"UPDATE Sells SET price = ? WHERE bar = ? AND beer = ?");
在执行PreparedStatement之前,我们需要提供相应的值去填充这些参数。这需要用到PreparedStatement类中的setXXX方法。常用的方法有:setInt, setFloat, setDouble, setString等。
接着上面的示例:
prepareUpdatePrice.setInt(1, 3);
prepareUpdatePrice.setString(2, "Bar Of Foo");
prepareUpdatePrice.setString(3, "BudLite");
________________________________________
执行 CREATE/INSERT/UPDATE 语句
DDL (data definition language) 语句,如建表,修改表结构,更新表内容都可以使用executeUpdate方法。示范如下:
Statement stmt = con.createStatement();
stmt.executeUpdate("CREATE TABLE Sells " +
"(bar VARCHAR2(40), beer VARCHAR2(40), price REAL)" );
stmt.executeUpdate("INSERT INTO Sells " +
"VALUES ('Bar Of Foo', 'BudLite', 2.00)" );
String sqlString = "CREATE TABLE Bars " +
"(name VARCHAR2(40), address VARCHAR2(80), license INT)" ;
stmt.executeUpdate(sqlString);
由于SQL语句并非恰好为一个字符串(或适合写在一行),所以需要使用加号(+)连结。特别注意在字符串"INSERT INTO Sells"后面的空格,为的是与字符串"VALUES"分开。
当使用executeUpdate执行DDL语句,返回值总是零(zero),当执行数据修改语句时将返回大于等零的值,即被修改的记录数。
int n = prepareUpdatePrice.executeUpdate() ;
________________________________________
执行 SELECT 语句
相对于前面的执行语句(DDL),查询语句执行后返回一个结果集合,并且不改变数据库的状态。相应地使用另一个方法executeQuery,返回一个ResultSet对象:
String bar, beer ;
float price ;
ResultSet rs = stmt.executeQuery("SELECT * FROM Sells");
while ( rs.next() ) {
bar = rs.getString("bar");
beer = rs.getString("beer");
price = rs.getFloat("price");
System.out.println(bar + " sells " + beer + " for " + price + " Dollars.");
}
查询语句返回的结果集被放在一个ResultSet实例中。我们可以访问结果集中的每一行记录(record)及每一行记录的属性。ResultSet提供一个游标(cursor),用于访问它的每一行记录。游标开始时指示在第一条记录的前面。可以使用next方法使游标指向下一条记录,如果下一条记录存在,next方法返回真(true),否则返回假(false)。
我们可以使用相应类型的getXXX方法获取某一行记录。在前面的示例中,我们用的是getString和getFloat方法访问某一列的值。我们需要提供列的名称作为方法的参数,并且注意VARCHAR2类型的bar、beer被转换成Java的String、REAL被转换成Java的float。
我们还可以使用列的顺序号代替列的名称来访问结果集,示范如下:
bar = rs.getString(1);
price = rs.getFloat(3);
beer = rs.getString(2);
________________________________________
访问ResultSet
JDBC还提供一套方法,让您得到游标当前处于结果集的位置,如getRow, isFirst, isBeforeFirst, isLast, isAfterLast。
这意味着游标允许自由访问结果集。默认的,游标只能向前滚动并且是只读的。当连接(Connection)建立了语句(Statement)后,您可以灵活地改变结果集(ResultSet)的滚动及更新模式。
Statement stmt = con.createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT * FROM Sells");
不同的选项包括: TYPE_FORWARD_ONLY、TYPE_SCROLL_INSENSITIVE和TYPE_SCROLL_SENSITIVE。您可以在需要只读游标时选用CONCUR_READ_ONLY、需要更新时选用CONCUR_UPDATABLE。默认的游标可以使用rs.next()向前滚动。使用可滚动的游标,您有更多的操作,示范如下:
rs.absolute(3); // 滚动到结果集的第三行
rs.previous(); // 回滚一行(第二行)
rs.relative(2); // 向前滚动两行(第四行)
rs.relative(-3); // 回滚三行(第一行)
________________________________________
事务(Transactions)
JDBC允许多条SQL语句作为一个事务。因此使用JDBC事务,我们能确保ACID (Atomicity, Consistency, Isolation, Durability)属性。
连接建立后,默认的事务模式是自动提交(auto-commit)。这使每一条SQL语句都被当作一个事务来处理。
我们可以关闭自动提交事务模式,如下:
con.setAutoCommit(false) ;
再次打开自动提交事务模式:
con.setAutoCommit(true) ;
一旦自动提交模式被关闭,除非您明确地使用commit()方法提交,否则每一条SQL语句都不会被提交。
con.commit() ;
在执行commit()方法之前,我们可以随时使用rollback()方法来回滚事务,取消之前的操作。
举例如下:
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.executeUpdate("INSERT INTO Sells VALUES('Bar Of Foo', 'BudLite', 1.00)" );
con.rollback();
stmt.executeUpdate("INSERT INTO Sells VALUES('Bar Of Joe', 'Miller', 2.00)" );
con.commit();
con.setAutoCommit(true);
请读者逐句理解上面的例子吧。
通常,事务回滚结合Java异常机制,被用于处理(不)可预见的错误。下一节我们将介绍Java的异常机制。
________________________________________
Handling Errors with Exceptions
错误无处不在。为保证程序的可恢复性,让数据库不至于由于某个错误导致崩溃。事务回滚与Java的异常机制提供了很优雅的解决办法。
客户(program)访问服务器(database)需要获得服务器的任务错误反馈。JDBC提供了SQLException用于访问这些错误。
结束事务回滚,举一个异常的例子:
try {
con.setAutoCommit(false) ;
stmt.executeUpdate("CREATE TABLE Sells (bar VARCHAR2(40), " +
"beer VARHAR2(40), price REAL)") ;
stmt.executeUpdate("INSERT INTO Sells VALUES " +
"('Bar Of Foo', 'BudLite', 2.00)") ;
con.commit() ;
con.setAutoCommit(true) ;
}catch(SQLException ex) {
System.err.println("SQLException: " + ex.getMessage()) ;
con.rollback() ;
con.setAutoCommit(true) ;
}
在上例中,由于beer被定义为VARHAR2(拼写错误),导致一个异常被抛出。错误的输出如下:
Message: ORA-00902: invalid datatype
假如您纠正了数据类型的错误,也可能会发生空间不足的错误,使您不能够建立一个新表。
需要对照原文,请访问:http://www-db.stanford.edu/~ullman/fcdb/oracle/or-jdbc.html