Java資料庫連接
Java資料庫連接,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面,提供了諸如查詢和更新資料庫中數據的方法。JDBC也是Sun Microsystems的商標[1]。JDBC是面向關係型資料庫的。
當前版本 | JDBC 4.3(2017年9月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驅動
這種類型的驅動通過客戶端載入資料庫廠商提供的本地程式碼庫(C/C++等)來訪問資料庫,而在驅動程式中則包含了Java程式碼。
優點:
- 速度快於第一類驅動(但仍比不上第3、第4類驅動)。
缺點
- 由於需要客戶端預裝對應的資料庫廠商程式碼庫,仍不適合Internet/Intranet應用。
類型3:網絡協定驅動
這種類型的驅動給客戶端提供了一個網絡API,客戶端上的JDBC驅動程式使用通訊端(Socket)來呼叫伺服器上的中介軟體程式,後者在將其請求轉化為所需的具體API呼叫。
優點:
- 不需要在客戶端載入資料庫廠商提供的程式碼庫,單個驅動程式可以對多個資料庫進行訪問,可延伸性較好。
缺點:
- 在中介軟體層仍需對最終數據進行組態;
- 由於多出一個中介軟體層,速度不如第四類驅動程式。
類型4:本地協定驅動
這種類型的驅動使用Socket,直接在客戶端和資料庫間通訊。
優點:
- 訪問速度最快;
- 這是最直接、最純粹的Java實現。
缺點:
- 幾乎只有資料庫廠商自己才能提供這種類型的JDBC驅動。
- 需要針對不同的資料庫使用不同的驅動程式。
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類型 |
---|---|
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) {}
}