Java資料庫連接

JAVA用于数据库访问的API

Java資料庫連接,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程式如何來訪問資料庫應用程式介面,提供了諸如查詢和更新資料庫中數據的方法。JDBC也是Sun Microsystems商標[1]。JDBC是面向關係型資料庫的。

JDBC
當前版本JDBC 4.3(2017年9月21日 (2017-09-21)
作業系統跨平台
類型Data access API
網站Java SE 7

J2SE中,提供了一個稱之為JDBC-ODBC橋(JDBC-ODBC Bridge[2])的API。通過ODBC,JDBC-ODBC橋驅動程式可以訪問所有支援ODBC的關係型資料庫。與JDBC API不同的是,這個驅動程式並不是由Java程式碼而是由機械碼(machine code)編寫,並且不是開放原始碼[3]

驅動程式類型

JDBC驅動程式共分四種類型:

類型1:JDBC-ODBC橋

這種類型的驅動把所有JDBC的呼叫傳遞給ODBC,再讓後者呼叫資料庫本地驅動程式碼(也就是資料庫廠商提供的資料庫操作二進制程式碼庫,例如Oracle中的oci.dll)。

優點:

  • 只要有對應的ODBC驅動(大部分資料庫廠商都會提供),幾乎可以訪問所有的資料庫。

缺點:

  • 執行效率比較低,不適合大數據量存取的應用;
  • 由於需要客戶端預裝對應的ODBC驅動,不適合Internet/Intranet應用。

類型2:本地API驅動

這種類型的驅動通過客戶端載入資料庫廠商提供的本地程式碼庫(CC++等)來訪問資料庫,而在驅動程式中則包含了Java程式碼。

優點:

  • 速度快於第一類驅動(但仍比不上第3、第4類驅動)。

缺點

  • 由於需要客戶端預裝對應的資料庫廠商程式碼庫,仍不適合Internet/Intranet應用。

類型3:網絡協定驅動

這種類型的驅動給客戶端提供了一個網絡API,客戶端上的JDBC驅動程式使用通訊端(Socket)來呼叫伺服器上的中介軟體程式,後者在將其請求轉化為所需的具體API呼叫。

優點:

  • 不需要在客戶端載入資料庫廠商提供的程式碼庫,單個驅動程式可以對多個資料庫進行訪問,可延伸性較好。

缺點:

  • 在中介軟體層仍需對最終數據進行組態;
  • 由於多出一個中介軟體層,速度不如第四類驅動程式。

類型4:本地協定驅動

這種類型的驅動使用Socket,直接在客戶端和資料庫間通訊。

優點:

  • 訪問速度最快;
  • 這是最直接、最純粹的Java實現。

缺點:

  • 幾乎只有資料庫廠商自己才能提供這種類型的JDBC驅動。
  • 需要針對不同的資料庫使用不同的驅動程式。

API概述

參看Java SE以及java.sql API

JDBC API主要位於JDK中的java.sql包中(之後擴充的內容位於javax.sql包中),主要包括(斜體代表介面,需驅動程式提供者來具體實現):

  • DriverManager:負責載入各種不同驅動程式(Driver),並根據不同的請求,向呼叫者返回相應的資料庫連接(Connection)。
  • Driver:驅動程式,會將自身載入到DriverManager中去,並處理相應的請求並返回相應的資料庫連接(Connection)。
  • Connection:資料庫連接,負責進行與資料庫間的通訊,SQL執行以及事務處理都是在某個特定Connection環境中進行的。可以產生用以執行SQL的Statement。
  • Statement:用以執行SQL查詢和更新(針對靜態SQL陳述式和單次執行)。
  • PreparedStatement:用以執行包含動態參數的SQL查詢和更新(在伺服器端編譯,允許重複執行以提高效率)。
  • CallableStatement:用以呼叫資料庫中的儲存程序
  • SQLException:代表在資料庫連接的建立和關閉和SQL陳述式的執行過程中發生了例外情況(即錯誤)。

資料類型的對映

從SQL到Java資料類型對映的JDBC規範
SQL類型 Java類型
CHAR java.lang.String
VARCHAR java.lang.String
LONGVARCHAR java.lang.String
NUMERIC java.math.BigDecimal
DECIMAL java.math.BigDecimal
BIT boolean
TINYINT byte
SMALLINT short
INTEGER int
BIGINT long
REAL float
FLOAT double
DOUBLE double
BINARY byte[]
VARBINARY byte[]
LONGVARBINARY byte[]
DATE java.sql.Date
TIME java.sql.Time
TIMESTAMP java.sql.Timestamp
BLOB java.sql.Blob
CLOB java.sql.Clob
Array java.sql.Array
REF java.sql.Ref
Struct java.sql.Struct

註:這種類型匹配不是強制性標準,特定的JDBC廠商可能會改變這種類型匹配。例如Oracle中的DATE類型是包含時分秒,而java.sql.Date僅僅支援年月日。

例子

利用Class.forName()方法來載入JDBC驅動程式(Driver)至DriverManager:

Class.forName( "com.somejdbcvendor.TheirJdbcDriver" );

然後,從DriverManager中,通過JDBC URL,用戶名,密碼來獲取相應的資料庫連接(Connection):

Connection conn = DriverManager.getConnection( 
      "jdbc:somejdbcvendor:other data needed by some jdbc vendor", // URL
      "myLogin", // 用户名
      "myPassword" ); // 密碼

不同的JDBC驅動程式的URL是不同的,它永遠以「jdbc:」開始,但後面的內容依照驅動程式類型不同而各異。在獲取Connection之後,便可以建立Statement用以執行SQL陳述式。下面是一個插入(INSERT)的例子:

 Statement stmt = conn.createStatement();
 stmt.executeUpdate( "INSERT INTO MyTable( name ) VALUES ( 'my name' ) " );

查詢(SELECT)的結果存放於結果集(ResultSet)中,可以按照順序依次訪問:

 Statement stmt = conn.createStatement();
 ResultSet rs = stmt.executeQuery( "SELECT * FROM MyTable" );
 while ( rs.next() ) {
     int numColumns = rs.getMetaData().getColumnCount();
     for ( int i = 1 ; i <= numColumns ; i++ ) {
        // 與大部分Java API中下標的使用方法不同,字段的下標從1開始
        // 當然,還有其他很多的方式(ResultSet.getXXX())獲取數據
        System.out.println( "COLUMN " + i + " = " + rs.getObject(i) );
     }
 }
 rs.close();
 stmt.close();

但是,通常,Java程式設計師們更傾向於使用PreparedStatement。下面的例子使用上例中的conn對象:

 PreparedStatement ps = null;
 ResultSet rs = null;
 try {
 ps = conn.prepareStatement( "SELECT i.*, j.* FROM Omega i, Zappa j
      WHERE i = ? AND j = ?" );
 // 使用問號作为參數的標示
 
 // 進行參數設置
 // 與大部分Java API中下标的使用方法不同,字段的下標從1開始,1代表第一个問號
 // 當然,還有其他很多針對不同類型的類似的PreparedStatement.setXXX()方法
 ps.setString(1, "Poor Yorick");
 ps.setInt(2, 8008);
 
 // 结果集
 rs = ps.executeQuery();
 while ( rs.next() ) {
     int numColumns = rs.getMetaData().getColumnCount();
     for ( int i = 1 ; i <= numColumns ; i++ ) {
        // 與大部分Java API中下标的使用方法不同,字段的下標從1開始
        // 當然,還有其他很多的方式(ResultSet.getXXX())獲取數據
        System.out.println( "COLUMN " + i + " = " + rs.getObject(i) );
     }
 
 }
 catch (SQLException e) {
  // 異常處理
 }
 finally { // 使用finally进行资源释放
  try {
   rs.close();
   ps.close();
  } catch( SQLException e){} // 异常處理:忽略close()时的错误
 }

如果資料庫操作失敗,JDBC將投擲一個SQLException。一般來說,此類異常很少能夠恢復,唯一能做的就是盡可能詳細的列印異常日記。推薦的做法是將SQLException翻譯成應用程式領域相關的異常(非強制處理異常)並最終轉返數據庫和通知用戶。

一個數據庫事務代碼如下:

boolean autoCommitDefault = conn.getAutoCommit();
try {
    conn.setAutoCommit(false); //关闭自动提交,从而将之后执行的多个SQL语句视为同一个事务(自动提交会使每一个SQL语句执行后提交一次而重新启用新的事务)
 
    /* 在此基于有事務控制的conn執行你的代碼 */
 
    conn.commit(); //最终提交该次事务的修改
} catch (Throwable e) {
    try { 
        conn.rollback(); //回退到上一个事务开始时的状态
    } catch (Throwable ignore) {}
    throw e;
} finally {
    try { conn.setAutoCommit(autoCommitDefault); } catch (Throwable ignore) {}
}

參考文獻

  1. ^ 存档副本. [2005-07-11]. (原始內容存檔於2009-12-05). 
  2. ^ 存档副本. [2005-07-11]. (原始內容存檔於2005-07-14). 
  3. ^ 存档副本. [2005-07-11]. (原始內容存檔於2010-11-19). 

外部連結

參見