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). 

外部連結

編輯

參見

編輯