JDBC API的那些事,你真的知道嗎?

AskHarries發表於2018-09-26

什麼是JDBC API?

概念糾正:

JDBC(Java Database Connectivity)

以下概念來自官方:
>

The JDBC API is a Java API for accessing virtually any kind of tabular data. (As a point of interest, JDBC is the trademarked name and is not an acronym; nevertheless, JDBC is often thought of as standing for “Java Database Connectivity.”) The JDBC API consists of a set of classes and interfaces written in the Java programming language that provide a standard API for tool/database developers and makes it possible to write industrial strength database applications using an all-Java API

JDBC API是一種Java API,用於訪問幾乎任何型別的表格資料。(作為一個興趣點,JDBC是商標名稱,並不是首字母縮略詞;然而,JDBC通常被認為代表“Java資料庫連線”。)JDBC API由一組用Java編寫的類和介面組成。為工具/資料庫開發人員提供標準API的程式語言,可以使用全Java API編寫工業級資料庫應用程式。

The JDBC API makes it easy to send SQL statements to relational database systems and supports all dialects of SQL. But the JDBC 2.0 API goes beyond SQL, also making it possible to interact with other kinds of data sources, such as files containing tabular data.

JDBC API可以輕鬆地將SQL語句傳送到關聯式資料庫系統,並支援所有SQL方言。但是JDBC 2.0 API超越了SQL,也使得與其他型別的資料來源(例如包含表格資料的檔案)進行互動成為可能。

The value of the JDBC API is that an application can access virtually any data source and run on any platform with a Java Virtual Machine. In other words, with the JDBC API, it isn’t necessary to write one program to access a Sybase database, another program to access an Oracle database, another program to access an IBM DB2 database, and so on. One can write a single program using the JDBC API, and the program will be able to send SQL or other statements to the appropriate data source. And, with an application written in the Java programming language, one doesn’t have to worry about writing different applications to run on different platforms. The combination of the Java platform and the JDBC API lets a programmer write once and run anywhere.

JDBC API的價值在於,應用程式幾乎可以訪問任何資料來源,並可以在任何具有Java虛擬機器的平臺上執行。換句話說,使用JDBC API,不必編寫一個程式來訪問Sybase資料庫,另一個程式訪問Oracle資料庫,另一個程式訪問IBM DB2資料庫,等等。可以使用JDBC API編寫單個程式,程式將能夠將SQL或其他語句傳送到適當的資料來源。而且,使用Java程式語言編寫的應用程式,人們不必擔心編寫不同的應用程式以在不同的平臺上執行。Java平臺和JDBC API的結合使程式設計師可以編寫一次並在任何地方執行。

The JDBC API is the industry standard for database-independent connectivity between the Java programming language and a wide range of databases. The JDBC API provides a call-level API for SQL-based database access. JDBC technology allows you to use the Java programming language to exploit “Write Once, Run Anywhere” capabilities for applications that require access to enterprise data.

JDBC API是Java程式語言與各種資料庫之間資料庫無關連線的行業標準。JDBC API為基於SQL的資料庫訪問提供了呼叫級API。JDBC技術允許您使用Java程式語言為需要訪問企業資料的應用程式利用“一次編寫,隨處執行”功能

JDBC API有什麼作用?

簡單來說,基於JDBC技術的驅動程式(“JDBC驅動程式”)可以做三件事:

  1. 建立與資料來源的連線
  2. 將查詢和更新語句傳送到資料來源
  3. 處理結果

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語句的執行過程中發生了例外情況(即錯誤)。

從根本上來說,JDBC是一種規範,它提供了一套完整的介面,允許對底層資料庫進行可移植的訪問,Java可以用於編寫不同型別的可執行檔案,例如:

  • Java Applications
  • Java Applets
  • Java Servlets
  • Java ServerPages(JSps)
  • Enterp JavaBeans(EJBs)

這些不同的可執行檔案都能夠使用JDBC驅動程式訪問資料庫,JDBC提供與ODBC相同的功能,允許Java程式包含與資料庫無關的程式碼

兩層和三層模型

JDBC API支援用於資料庫訪問的兩層和三層模型。

  1. 資料庫訪問的雙層體系結構
image
image

在雙層模型中,Java applet或應用程式直接與資料來源對話。這需要一個JDBC驅動程式,它可以與正在訪問的特定資料來源進行通訊。使用者的命令被傳遞到資料庫或其他資料來源,並且這些語句的結果被髮送回使用者。資料來源可以位於使用者通過網路連線的另一臺機器上。這被稱為客戶端/伺服器配置,使用者的機器作為客戶端,機器將資料來源作為伺服器。網路可以是內聯網,例如,連線公司內的員工,或者可以是因特網

  1. 資料庫訪問的三層體系結構
    image
    image

在三層模型中,命令被髮送到服務的“中間層”,然後將命令傳送到資料來源。資料來源處理命令並將結果傳送回中間層,然後中間層將它們傳送給使用者。MIS主管發現三層模型非常具有吸引力,因為中間層可以保持對訪問的控制以及可以對公司資料進行的更新。另一個優點是它簡化了應用程式的部署。最後,在許多情況下,三層架構可以提供效能優勢。直到最近,中間層通常使用C或C ++等語言編寫,這些語言提供了快速的效能。但是,隨著優化編譯器的引入,將Java位元組碼轉換為高效的機器特定程式碼和技術,例如Enterprise JavaBeanstm,Java平臺正迅速成為中間層開發的標準平臺。這是一個很大的優勢,可以利用Java的健壯性,多執行緒和安全功能。隨著企業越來越多地使用Java程式語言編寫伺服器程式碼,JDBC API在三層體系結構的中間層中越來越多地被使用。使JDBC成為伺服器技術的一些功能是它支援連線池,分散式事務和斷開連線的行集。當然,JDBC API允許從Java中間層訪問資料來源。

SQL 一致性

SQL是訪問關聯式資料庫的標準語言。不幸的是,SQL還沒有像人們想的那樣標準化。

一個難點是不同DBMS(資料庫管理系統)使用的資料型別有時會有所不同,並且變化可能很大。JDBC通過在類中定義一組通用SQL型別識別符號來處理此問題java.sql.Types。請注意,在本資料中使用的術語“JDBC SQL型別”,“JDBC型別”和“SQL型別”是可互換的,並且是指在中定義的通用SQL型別識別符號java.sql.Types。在“對映SQL和Java型別”一章中有關於資料型別一致性的更完整的討論。

SQL一致性的另一個難點是儘管大多數DBMS使用標準形式的SQL來實現基本功能,但它們不符合最近定義的標準SQL語法或更高階功能的語義。例如,並非所有資料庫都支援儲存過程或外部聯接,並且那些資料庫並不總是相互一致。此外,對SQL3功能和資料型別的支援也有很大差異。希望真正標準的SQL部分將擴充套件為包含越來越多的功能。但是,在此期間,JDBC API必須支援SQL。

JDBC API處理此問題的一種方法是允許將任何查詢字串傳遞給底層DBMS驅動程式。這意味著應用程式可以根據需要自由使用盡可能多的SQL功能,但是它存在在某些DBMS上收到錯誤的風險。實際上,應用程式查詢可能不是SQL,或者它可能是為特定DBMS設計的SQL的專用衍生物(例如,用於文件或影像查詢)。

JDBC處理SQL一致性問題的第二種方法是提供ODBC樣式的轉義子句。轉義語法為SQL分歧的幾個更常見的領域提供了標準的JDBC語法。例如,日期文字和儲存過程呼叫都有轉義。

對於複雜的應用程式,JDBC以第三種方式處理SQL一致性。它通過介面提供有關DBMS的描述性資訊,DatabaseMetaData以便應用程式可以適應每個DBMS的要求和功能。但是,典型的終端使用者無需擔心後設資料。

由於JDBC API用作開發資料庫訪問工具和其他API的基本API,因此它還必須解決構建在其上的任何內容的一致性問題。JDBC驅動程式必須至少支援ANSI SQL-92 Entry Level。(ANSI SQL-92指的是1992年美國國家標準協會採用的標準。入門級指的是SQL功能的特定列表。)但是,請注意,儘管JDBC 2.0 API包括對SQL3和SQLJ的支援,但JDBC驅動程式不需要支援他們。

鑑於資料庫供應商,連線供應商,Internet服務供應商和應用程式編寫者廣泛接受JDBC API,它已成為Java程式語言資料訪問的標準。

常見的JDBC元件

JDBC API提供一下介面和類

  • DriverManager: 該類管理資料庫驅動列表.使用通訊自協議匹配來自Java應用程式的連線請求和正確的資料庫驅動程式.識別JDBC下某個子協議的第一個驅動程式將用於建立資料庫連線.
  • Driver: 此介面處理與資料庫伺服器的通訊. 您將很少直接與Driver物件進行互動.而是使用DriverManager物件來管理此類物件.它還抽象了與使用Driver物件相關的細節.
  • Connection:此介面包含用於聯絡資料庫的所有方法.連線物件表示通訊上下文.即,與資料庫的所有通訊僅通過連線的物件.
  • Statement: 您使用此介面建立的物件將SQL語句提交到資料庫,除執行儲存過程外,某些派生介面還接受引數.
  • ResultSet: 在使用Statement物件執行SQL查詢後,這些物件儲存從資料庫檢索的資料,它充當迭代器,允許您遍歷其資料.
  • SQLException: 此類處理資料庫應用程式中發生的任何錯誤.

JDBC驅動程式型別

JDBC驅動程式實現了在JDBC API中定義的介面,用於於資料庫伺服器互動。例如:使用JDBC驅動程式可以通過傳送SQL或資料庫命令,然後使用Java接收結果,讓你開啟資料庫連線並與資料庫進行互動。JDK附帶的 Java.sql包中包含了各種類,其行為已定義,其實際實現在第三方驅動程式中完成。第三方供應商在其資料庫驅動程式中實現java.sql.Driver介面。下圖顯示了各種驅動程式實現的可能性:

image
image

在開發JDBC API之前,Microsoft的ODBC(開放資料庫連線)API是用於訪問關聯式資料庫的最廣泛使用的程式設計介面。

JDBC驅動程式實現因Java執行的各種作業系統和硬體平臺而異,Sun將實現型別分為四種型別:

Type 1:JDBC-ODBC Bridge Driver

該型別驅動程式中,JDBC橋接器用於訪問安裝在每臺客戶機上的ODBC驅動程式。驅動把所有JDBC的呼叫傳遞給ODBC,再讓後者呼叫資料庫本地驅動程式碼(也就是資料庫廠商提供的資料庫操作二進位制程式碼庫,例如Oracle中的oci.dll),使用ODBC需要在系統上配置表示目標資料庫的資料來源名稱(DSN)
當Java第一次出現時,這是一個有用的驅動程式,因為大多數的資料庫只支援ODBC訪問,但現在推薦這種型別的驅動程式僅用於實驗用途或沒有其他代替方案可用

image
image

優點:

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

缺點:

  • 執行效率比較低,不適合大資料量訪問的應用
  • 由於需要客戶端預裝對應的ODBC驅動,不適合Internet/Intranet應用

Type 2:JDBC-Native API

在該型別驅動程式中,驅動通過客戶端載入資料庫廠商提供的原生程式碼庫(C/C++等)來訪問資料庫,JDBC API呼叫將轉換為本機C/C++ API呼叫,這些呼叫對於資料庫是唯一的。與JDBC-ODBC Bridge的使用方式相同,必須在每臺客戶端計算機上安裝供應商特定的驅動程式。
如果我們更改資料庫,我們必須更改本機API,因為它是特定於資料庫的而且現在幾乎已經過時,但是你可能會通過該型別的驅動程式實現一些速度提升,因為它消除了ODBC的開銷

image
image

優點:

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

缺點:

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

Type 3:JDBC-Net pure Java (網路協議)

此驅動程式給客戶端提供了一個網路API,客戶端上的JDBC使用套接字(Socket)來呼叫伺服器上的中介軟體程式,該程式將JDBC呼叫轉換為獨立於DBMS的網路協議,然後由伺服器將其轉換為DBMS協議並轉發到資料庫伺服器。這個網路伺服器中介軟體能夠將其純Java客戶端連線到許多不同的資料庫。使用的具體協議取決於供應商。通常,這是最靈活的JDBC替代方案,它不需要在客戶端上安裝程式碼。該解決方案的所有供應商都可能提供適合Intranet使用的產品。為了使這些產品也支援Internet訪問,它們必須處理Web強加的安全性,通過防火牆訪問等的附加要求。

image
image

可以將應用程式伺服器視為JDBC“代理”,這意味著它會呼叫客戶端應用程式。因此,需要了解應用程式伺服器的配置,以便有效地使用此驅動程式。

優點:

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

缺點:

  • 在中介軟體層仍需對最終資料進行配置
  • 由於多出一箇中介軟體層,速度不如第四類驅動程式

Type 4:Pure Java

這種型別的驅動程式將JDBC呼叫直接轉換為DBMS使用的網路協議。這允許從客戶端計算機直接呼叫DBMS伺服器,是Intranet訪問的絕佳解決方案。由於許多協議都是專有的,因此資料庫供應商本身就是主要來源。

在Type 4驅動程式中,基於純Java的驅動程式通過套接字連線與供應商的資料庫直接通訊。這是資料庫可用的最高效能驅動程式,通常由供應商自己提供。
這種驅動是非常靈活的,不需要在客戶端或伺服器上安裝特殊的軟體。此外,這些驅動程式可以動態下載。

image
image

優點:

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

缺點:

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

MySql的 Connector/J 就是Type 4驅動程式。由於其網路協議的專有性質,資料庫供應商通常提供Type 4驅動程式。

JDBC 體系結構

JDBC API包含兩組主要的介面:第一組是應用程式編寫者的JDBC API,第二組是驅動程式編寫者的低階JDBC驅動程式API。應用程式和applet可以使用基於純Java JDBC技術的驅動程式通過JDBC API訪問資料庫,如圖所示:

image
image

左側Type 4,右側Type 3

下圖說明了使用ODBC驅動程式和現有資料庫客戶端庫的JDBC連線:

image
image

左側Type 1,右側Type 2

應該使用哪種驅動程式?

驅動類別|全Java|網路連線
-|-|-
JDBC-ODBC Bridge|No|Direct
Native API as basis|No|Direct
JDBC-Net|client Yes/server Maybe|Indirect
Native protocol as basis|Yes|Direct

  • Direct:JDBC客戶端直接與DBMS伺服器建立的連線,該伺服器可能是遠端的
  • Indirect:JDBC客戶端與中介軟體程式建立的連線,充當DBMS伺服器的橋樑

驅動程式Type 3和4是使用JDBC API訪問資料庫的首選方法。驅動程式Type 1和2是臨時解決方案,其中尚未提供直接純Java驅動程式。對於需要中介軟體的Type 1和Type 2(下表中未顯示)可能存在差異,但這些通常是不太理想的解決方案。Type 3和4提供了Java技術的所有優點

  • 如果要訪問某種型別的資料庫(如Oracle,Sybase或IBM),則首選Type 4驅動程式
  • 如股票Java應用程式同時訪問多種型別的資料庫,則Type 3是首選驅動程式
  • 當Type 3或Type 4驅動程式不可用的情況下采用Type 2驅動程式
  • Type 1驅動程式不被視為部署及驅動程式,通常僅用於開發和測試

JDBC-Datebase Connections

安裝了適當的驅動程式後,就可以使用JDBC簡歷資料庫連線了.以下是四個基本步驟:

  • 匯入JDBC包:需要包含資料庫變成所需的JDBC類的包.大多數情況下,
    使用
    import sql.*
    就夠了
  • 註冊JDBC驅動程式:需要初始化驅動程式,將JVM所需的驅動程式實現載入到記憶體中,以便可以開啟與資料庫的通訊通道.
  • 開啟一個連線: 需要使用DriverManager.getConneection()方法建立一個Conection物件,它表示與資料庫的物理連線.
  • 執行查詢:需要使用型別為Statement的物件來構建和提交SQL語句到資料庫.
  • 從結果集中提取資料:需要使用相應的ResultSet.getXXX()方法從結果集中檢索資料.
  • 清理環境: 需要明確的關閉所有資料庫資源,而不依賴於JVM的垃圾回收.
匯入JDBC包

import
語句告訴Java編輯器在哪裡找到你在程式碼中引用的類的原始碼位置.要使用標準JDBC包(允許在SQL表中選擇,插入,更新和刪除資料),請將以下匯入新增到原始碼中:

impor java.sql.*;
複製程式碼
註冊JDBC驅動程式

在實用程式之前,必須先註冊該驅動程式,將驅動程式的類檔案載入到記憶體中,因此可以將其用作JDBC介面的實現。

Class.forName();

註冊驅動程式最常用的方法是使用Java的Class.forName()將驅動程式的類檔案動態載入到記憶體中,自動註冊他。此方法是首選,因為它使驅動程式的註冊可配置和可移植。

try{
    Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e){
    //
}
複製程式碼

註冊驅動實際執行下面程式碼:

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}
複製程式碼
資料庫URL配置

可以使用DriverManager.getConnection() 方法建立連線。為了便於參考,這裡列出三個過載的方法:

  • getConnection(String url)
  • getConnection(String url,Properties prop)
  • getConnection(String url,String user,String password)

每個方法都需要一個資料庫URL,下表列出了常用的JDBC、驅動程式名稱和資料庫URL。

RDBMS|JDBC驅動程式名稱|網址格式
-|-|-
MySQL|com.mysql.jdbc.Driver|jdbc:myql://hostname/databaseName
ORACLE|oracle.jdbc.driver.OracleDriver|jdbc:oracle:thin:@hostname:port:databaseName
DB2|com.ibm.db2.jdbc.net.DB2Driver|jdbc:db2: hostname:port/databaseName
Sysbase|com.sybase.jdbc.SybDriver|jdbc:sybase:Tds: hostname:port/databaseName

URl中突出顯示的部分都是靜態的,只需要根據資料庫設定更改剩餘部分。

建立連線物件

資料庫服務安裝在本機,埠號為3306,使用者名稱為root,密碼為root,資料庫名是test。

  • 使用帶使用者名稱和密碼的資料庫URL
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url,user,password);
複製程式碼
  • 僅使用資料庫URL
String url = "jdbc:mysql://localhost:3306/test?user=root&password=root";
Connection connection = DriverManager.getConnection(url);
複製程式碼
  • 使用資料庫URL和Properties物件
String url = "jdbc:mysql://localhost:3306/test";
Properties info = new Properties();
info.put("user","root");
info.put("password","root");
Connection connection = DriverManager.getConnection(url,info);
複製程式碼
關閉JDBC連線

在JDBC程式結束時,需要明確關閉所有的資料庫連線。如果在程式中忘了關閉,Java垃圾回收器將在清理果實物件時關閉該連線。
但是依靠垃圾回收,特別是資料庫程式設計,是一個很不好的程式設計實踐。所以應該要養成使用與連線物件關聯的close()方法關閉連線的習慣。
要確保連線已關閉,可以將關閉連線的程式碼編寫在“finally”塊中,不管是否發生異常“finally”塊最後總是會執行。

connection.close();
複製程式碼

Statements、PreparedStatement和CallableStatement

一旦獲得連結,我們就可以與資料庫進行互動。JDBC Statement,PreparedStatement和CallableStatement介面定義了能夠傳送SQL或PL/SQL命令以及從資料庫接收資料的方法和屬性。
他們還定義了有助於橋接資料庫中使用的Java和SQL資料型別之間的資料型別差異的方法。
下表提供了每個介面確定要使用的介面的目的的摘要。

介面|推薦用途
-|-
Statement|用於對資料庫的通用訪問。在執行時使用靜態SQL語句時很有用。Statement介面不能接收引數。
PreparedStatement|當你計劃多次使用SQL語句時使用。PreparedStatement介面在執行時可以接收輸入引數。
CallableStatement|在想要訪問資料庫儲存過程時使用。CallableStatement介面也可以接受執行時輸入的引數。

  1. Statement
Statement stmt = null;
try{
    stmt = connection.createStatement();
}catch(SQLException e){
    ...
}finally{
    stmt.close();
}
複製程式碼

在建立Statement物件後,可以使用它來執行一個SQL語句,它有三個方法可以執行。分別是:

  • boolean execute(String sql):如果可以檢索到ResultSet物件,則返回一個布林值true,否則返回false。使用此方法執行SQL DDL語句或者需要使用真正的動態SQL,可以使用執行建立資料庫,建立表的SQL語句等等。
  • int executeUpdate(String sql):返回受執行SQL語句所影響的行數。使用次方法執行預期會影響多行的SQL語句,例如:INSERT、UPDATE或DELETE語句。
  • ResultSet executeQuery(String sql):返回一個ResultSet物件。當您西往獲得結果集時,請使用此方法,就像使用SELECT語句一樣。

關閉Statement物件

就像關閉Connection物件以儲存資料庫資源一樣,出於同樣的原因,也應該關閉Statement物件。如果先關閉Connection物件,它也會關閉Statement物件。但是應該明確關閉Statement物件以確保正確的清理順序。

  1. PreparedStatement物件

    PreparedStatement擴充套件了Statement介面,它提供了一些通用Statement額外的功能。這個語句具有動態提供引數的靈活性。

PreparedStatement pstmt = null;
try{
    String sql = "UPDATE Employees SET age = ? WHERE id = ?";
    pstmt = connection.prepareStatement(sql);
    pstmt.setInt(1,18);
    ...
}catch(SQLException e){
    ...
}finally{
    pstmt.close();
}
複製程式碼
  • JDBC中的所有引數都有‘?’符號作為佔位符,這被稱為引數標記。在執行SQL語句之前,必須為每個引數(佔位符)提供值。
    setXXX(int parameterIndex, XXX x) 方法將值繫結到引數,其中XXX表示繫結到輸入引數的值的Java資料型別。如果忘記提供值,將丟擲一個SQLException。
  • 每個引數標記是它其位置序號的引用。第一個標記表示位置1,下一個位置2等等。該方法與Java陣列下標不同(從0開始)
  • 所有Statement物件與資料庫互動的方法:execute(),executeQuery()和executeUpdate()也可以用於PreparedStatement物件。但是,這些方法被修改為可以使用輸入引數的SQL語句。
關閉PreparedStatement物件

如果先關閉Connection物件,也會關閉PreparedStatement物件。但是,應該明確的關閉PreparedStatement物件以確保正確順序清理資源。

  1. CallableStatement物件
    類似Connection物件建立Statement和PreparedStatement物件一樣,它還可以使用桐言的方式建立CallableStatement物件,該物件將用於執行對資料庫儲存過程的呼叫。

假如需要執行以下MySQL儲存過程:

DELIMITER $$

DROP PROCEDURE IF EXISTS 'EMP'.'getName' $$
CREATE PROCEDURE 'EMP'.'getName'
    (IN EMP_ID INT,OUT EMP_FIRST VARCHAR(255)
BEGIN 
    SELECT first INTO EMP_FIRST
    FROM Employees
    WHERE ID = EMP_ID;
END $$

DELIMITER ;
複製程式碼

存在三種型別的引數:IN,OUT和INOUT。PreparedStatement物件僅使用IN引數。CallableStatement物件可以使用這三個。
以下是上面三種型別引數的定義:

引數|描述
-|-
IN|建立SQL語句是其引數值是未知的。使用setXXX()方法將值繫結到IN引數。
OUT|有SQL語句返回的引數值。可以使用getXXX()方法從OUT引數中檢索值。
INOUT|提供輸入和輸出值的引數。使用setXXX()方法繫結變數並使用getXXX()方法檢索值。

CallableStatement cstmt = null;
try{
    String sql = "{call getEmpName (?,?)}";
    cstmt = connection.prepareCall(sql);
    ...
}catch(SQLException e){
    ...
}finally{
    cstmt.close();
}
複製程式碼

String變數sql表示儲存過程,帶有兩個引數佔位符。
使用CallableStatement物件就像使用PreparedStatement物件一樣。在執行語句之前,必須將值繫結到所有引數。否則將丟擲一個SQLException。
如果有IN引數,只需遵循適用於PreparedStatement物件的相同規則和技術。
使用OUt和INOUT引數時,必須使用一個額外的CallableStatement物件方法registerOutParameter()。registerOutParamter()方法將JDBC資料型別繫結到儲存過程並返回預期資料型別。
當呼叫儲存過程,可以使用適當的getXXX()方法從OUT引數中檢索該值,此方法將檢索到的SQL型別的值轉換為對應的Java資料型別。

關閉CallableStatement物件

就像關閉其他Statement物件一樣,由於同樣的原因(節省資料庫系統資源),還應該關閉CallableStatement物件。

簡單的呼叫close()方法將執行關閉CallableStatement物件。 如果先關閉Connection物件,它也會關閉CallableStatement物件。 但是,應該始終顯式關閉CallableStatement物件,以確保按正確順序的清理資源。

JDBC-Result Sets

SQL語句執行後從資料庫,返回的資料放在結果集中。SELECT語句用於從資料庫中選擇行並在結果集中檢視它們的標準方法。java.sql.ResultSet介面表示資料庫查詢的結果集。

ResultSet物件維護指向結果集中當前行的遊標。術語”結果集“指的是ResultSet物件中包含的行和列資料。

ResultSet介面的方法可以分為三類:

  • 導航方法:用於移動遊標。
  • 獲取方法:用於檢視遊標指向的當前行的列中的資料
  • 更新方法:用於更新當前行的列中的資料。然後,也可以在底層資料庫中更新。

遊標可根據ResultSet的屬性移動。在建立生成ResultSet的對應Statement時,將指定這些屬性。

JDBC提供以下連結方法來建立具有所需ResultSet的語句:

  • createStatement(int resultSetType,int resultSetConcurrency)
  • prepareStatement(String sql,int resultSetType,int resultSetConcurrency)
  • prepareCall(String sql,int resultSetType,int resultSetConcurrency)

resultSetType參數列示ResultSet物件的型別,resultSetConcurrency引數指定結果集是隻讀還是可更新。

Type of ResultSet

可以指定的的resultSetType型別如下:(如果不指定任何resultSetType型別,將自動分配一個TYPE_FORWARD_ONLY值)

型別|描述
-|-
ResultSet.TYPE_FORWARD_ONLY|遊標只能在結果集中向前移動
ResultSet.TYPE_SCROLL_INSENSITIVE|遊標可以向前和向後滾動,結果集對建立結果集後發生的資料庫所做的更改不敏感
ResultSet.TYPE_SCROLL_SENSITIVE|遊標可以向前和向後滾動,結果集對建立結果集之後的其他資料庫的更改敏感

Concurrency of ResultSet

可以制定的resultSetConcurrency型別如下:(如果不指定任何resultSetConcurrency型別,將自動分配一個CONCUR_READ_ONLY值)

併發性|描述
-|-
ResultSet.CONCUR_READ_ONLY|建立只讀結果集,這是預設值。

ResultSet.CONCUR_UPDATABLE|建立可更新的結果集。

瀏覽結果集
方法|描述
-|-
public void beforeFirst() throw SQLException|將遊標移動到第一行之前
public void afterLast() throw SQLException|將遊標移動到最後一行之後
public boolean first() throw SQLException|將遊標移動到第一行
public void last() throw SQLException|將遊標移動到最後一行
public boolean absolute (int row) throw SQLException|將遊標移動到指定行
public boolean relative (int row) throw SQLException|將遊標從當前位置向前或向後移動給定的行數
public boolean previous () throw SQLException|將遊標移動到上一行
public boolean next() throw SQLException|將遊標移動到下一行
public int getRow() throw SQLException|返回遊標指向的行號
public void moveToInsertRow () throw SQLException|將遊標移動到結果集中的一個特殊行,該行可用於將新航插入資料庫

public void moveToCurrentRow () throw SQLException|如果遊標位於插入行,將游標移動到記住的游標位置,通常為當前行。如果游標不位於插入行上,則此方法無效。

檢視結果集

每個可能的資料型別都有get方法,每個get方法都有兩個版本:

  • 採用列名稱
  • 採用列索引

方法|描述
-|-
public xxx getXXX(String columnName) throws SQLException|返回當前行名為columnName的列中值

public xxx getXXX(int columnIndex) throw SQLException|返回當前行中指定列索引的值。列索引從1開始,意味著行的第一列為1

更新結果集

ResultSet介面包含一組用於更新結果集的資料的更新方法。與get方法一樣,每種型別都有兩種:

  • 採用列名稱
  • 採用列索引

例如,要更新結果集當前行的String例:
方法|描述
-|-
public void updateString(int columnIndex,String s) throw SQLException|將指定列中的String值更改為s的值
public void updateString(String columnName,String s) throw SQLException|將列名為columnName的String值更改為s的值

更新結果集中的行會更改ResultSet物件中當前行的列,但不會更改基礎資料庫中的列。要想對資料庫中的行進行更改,需要呼叫以下方法:

方法|描述
-|-
public void updateRow()|通過更新資料庫中相對應行來更新當前行
public void deleteRow()|從資料庫中刪除當前行
public void refreshRow()|重新整理結果集中的資料以反映資料庫中的任何最新更改
public void cancelRowUpdates()|取消對當前行進行的任何更新

public void insertRow()|在資料庫中插入一行。只有在遊標指向插入行時才能呼叫此方法。

資料型別

JDBC驅動程式將Java資料型別轉換為適當的JDBC型別。然後將其傳送到資料庫。它為大多數資料型別提供並使用預設對映。例如:Java int型別會被轉換成SQL INTERGER。建立預設對映以提供驅動程式之間的一致性

下表總結了在嗲用PreparedStatement或CallableStatement物件的setXXX()方法或ResultSet.updateXXX()方法時,Java資料型別轉換為JDBC的預設資料型別:
SQL型別|JDBC/Java型別|setXXX|updateXXX
-|-|-|-
VARCHAR|java.lang.String|setString|updateString
CHAR|java.lang.String|setString|updateString
LONGVARCHAR|java.lang.String|setString|updateString
BIT|boolean|setBoolean|updateBoolean
NUMERIC|java.math.BigDecimal|setBigDecimal|updateBigDecimal
TINYINT|byte|setByte|updateByte
SMALLINT|short|setShort|updateShort
INTEGER|int|setInt|updateInt
BIGINT|long|setLong|updateLong
REAL|float|setFloat|updateFloat
FLOAT|float|setFloat|updateFloat
DOUBLE|double|setDouble|updateDouble
VARBINARY|byte[ ]|setBytes|updateBytes
BINARY|byte[ ]|setBytes|updateBytes
DATE|java.sql.Date|setDate|updateDate
TIME|java.sql.Time|setTime|updateTime
TIMESTAMP|java.sql.Timestamp|setTimestamp|updateTimestamp
CLOB|java.sql.Clob|setClob|updateClob
BLOB|java.sql.Blob|setBlob|updateBlob
ARRAY|java.sql.Array|setARRAY|updateARRAY
REF|java.sql.Ref|setRef|updateRef
STRUCT|java.sql.Struct|setStruct|updateStruct

JDBC 3.0增強了對BLOB,CLOB,ARRAY和REF資料型別的支援。 ResultSet物件現在具有updateBLOB(),updateCLOB(),updateArray()和updateRef()方法,使您能夠直接運算元據庫伺服器上的相應資料。

setXXX()和updateXXX()方法可以將特定的Java型別轉換為特定的JDBC資料型別。方法setObject()和updateObject()可以將幾乎任何Java型別對映到JDBC資料型別。

ResultSet物件為每個資料型別提供了相應的get方法來檢索列值。每個方法都可以使用列明或其序號位置來檢索值。

SQL|JDBC/Java|getXXX
-|-|-
VARCHAR|java.lang.String|getString
CHAR|java.lang.String|getString
LONGVARCHAR|java.lang.String|getString
BIT|boolean|getBoolean
NUMERIC|java.math.BigDecimal|getBigDecimal
TINYINT|byte|getByte
SMALLINT|short|getShort
INTEGER|int|getInt
BIGINT|long|getLong
REAL|float|getFloat
FLOAT|float|getFloat
DOUBLE|double|getDouble
VARBINARY|byte[ ]|getBytes
BINARY|byte[ ]|getBytes
DATE|java.sql.Date|getDate
TIME|java.sql.Time|getTime
TIMESTAMP|java.sql.Timestamp|getTimestamp
CLOB|java.sql.Clob|getClob
BLOB|java.sql.Blob|getBlob
ARRAY|java.sql.Array|getARRAY
REF|java.sql.Ref|getRef

STRUCT|java.sql.Struct|getStruct

事務

如果JDBC連線處於自動提交模式(預設情況下),那麼每個SQL語句在完成後都會提交到資料庫
對於簡單的應用程式可能沒問題,但是有三個原因需要考慮是否關閉自動提交併管理自己的事務:

  • 提高效能
  • 保證業務流程的完整性
  • 使用分散式事務

事務能夠控制何時將更改提交併應用於資料庫。它將單個SQL語句或一組SQL語句視為一個邏輯單元,如果任何語句失敗,則整個事務失敗
要啟動手動事務支援,需呼叫Connection物件的setAutoCommit()方法。如果將布林值false傳給setAutoCommit(),則關閉自動提交,也可以傳遞一個true來重新開啟它

connection.setAutoCommit(false);
複製程式碼

提交和回滾

完成更改後,若要提交更改,那麼可呼叫connection物件的commit()方法

connection.commit();
複製程式碼

如果要回滾資料庫更新,使用以下程式碼:

connection.rollback();
複製程式碼

以下示例展示瞭如果使用提交和回滾:

try{
    connection.setAutoCommit(false);
    Statement stmt = connection.createStatement();
    String sql = "INSERT INTO Employees VALUES (123,'java');
    stmt.executeUpdate(sql);
    connection.commit();
}catch(SQLException e){
    connection.rollback();
}finally{
    //release resource
}
複製程式碼

Savepoint

新的JDBC 3.0新增了Savepoint介面提供了額外的事務控制能力。大多數現代DBMS支援其環境中的儲存點,
設定Savepoint時,可以在事務中定義邏輯回滾點。如果在Savepoint之後發生錯誤,則可以使用回滾方法來撤銷所有更改或僅儲存儲存點之後所做的更改。
Connection物件由兩個新方法管理儲存點:

  • Savepoint setSavepoint(String name):定義新的儲存點並返回該物件
  • void releaseSavepoint(Savepoint savepoint):刪除儲存點。
    有一個void rollback(Savepoint savepoint)方法,它將使用事務回滾到制定的儲存點。
Savepoint savepoint = null;
try{
    connection.setAutoCommit(false);
    Statement stmt = connection.createStatement();
    String sql = "INSERT INTO Employees VALUES (123,'Java')";
    stmt.executeUpdate(sql);
    savepoint = connection.setSavepoint("Savepoint");
    sql = "INSERT INTO Employees VALUES (456,'C++')";
    stmt.executeUpdate(sql);
    sql = "INSERT INTO Employees VALUES ('789','Python')";//error sql
    stmt.executeUpdate(sql);
    connection.commit();
}catch(SQLException e){
    if (savepoint == null){
        connection.rollback();
    }else{
        connection.rollback(savepoint);
        connection.commit();    
    }
}finally{
    //release resource
}
複製程式碼

在這種情況下,第二條INSERT不會成功。

批量處理

批量處理允許將相關的SQL語句分組到批處理中,並通過對資料路的一次呼叫來提交它們。一次向資料庫傳送多個SQL語句,可以減少通訊開銷,從而提高效能

  • 應使用DatabaseMetaData的supportsBatchUpdates()方法來確定目標資料庫是否支援批量更新。如果JDBC驅動程式支援此功能,該方法將返回true
  • Statement、PreparedStatement和CallableStatement的addBatch()方法用於將單個語句新增到批處理。
  • executeBatch()用於執行分在一組的所有語句並返回一個int陣列,陣列的每個元素表示相應的更新語句的更新計數
  • clearBatch()方法可以刪除使用addBatch()方法新增的所有語句。但是無法選擇要刪除某個語句

使用Statement物件進行批處理

  • 使用connection.createStatement()方法建立Statement物件
  • 使用connection.setAutoCommit(false)取消自動提交
  • 使用statement.addBatch(String sql)方法將SQL語句新增到批處理中
  • 使用statement.executeBatch()方法執行所有SQL語句
  • 使用connection.commit()方法提交所有更改
//create Statement object
Statement stmt = connection.createStatement();

//set auto-commit to false
connection.setAutoCommit(false);

//create some SQL statement
String sql1 = "INSERT INTO Employees VALUES (123,'Java')";
String sql2 = "INSERT INTO Employees VALUES (456,'C++')";

//add above SQL statement in the batch
stmt.addBatch(sql1);
stmt.addBatch(sql2);

//execute batch tasks
int[] result = stmt.executeBatch();

//commit transaction
connection.commit();
複製程式碼

使用PreparedStatement物件進行批處理

  • 建立帶有佔位符的SQL語句
  • 使用connection.prepareStatement()方法建立PreparedStatement物件
  • 使用connection.setAutoCommit(false)方法取消自動提交
  • 使用preparedStatement.addBatch(String sql)方法將SQL語句新增到批處理中
  • 使用preparedStatement.executeBatch()方法執行所有SQL語句
  • 使用connection.commit()方法提交所有更改
//create SQL Statement
String sql = "INSERT INTO Employees VALUES (?,?)";

//create PreparedStatement object
PreparedStatement pstmt = connection.prepareStatement(sql);

//set auto-commit to false
connection.setAutoCommit(false);

//set the variables
pstmt.setInt(123);
pstmt.setString("Java");
pstmt.addBatch();

 //set the variables
pstmt.setInt(456);
pstmt.setString("C++");
pstmt.addBatch();

//execute batch tasks
int[] result = pstmt.executeBatch();

//commit transaction
connection.commit();
複製程式碼

參考文獻

Getting Started with the JDBC API:https://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/GettingStartedTOC.fm.html

JDBC Tutorial:https://www.tutorialspoint.com/jdbc/index.htm

JDBC API的那些事,你真的知道嗎?


相關文章