0x01 前言
JDBC(Java DataBase Connectivity,java 數(shù)據(jù)庫連接)是一種用于執(zhí)行 SQL 語句的 Java API坎拐,可以為多種關(guān)系數(shù)據(jù)庫提供統(tǒng)一訪問桨昙,它由一組用 Java 語言編寫的類和接口組成。JDBC 提供了一種基準(zhǔn),據(jù)此可以構(gòu)建更高級的工具和接口啥容,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應(yīng)用程序。
JDBC架構(gòu)
JDBC API 支持兩層和三層處理模型進(jìn)行數(shù)據(jù)庫訪問顷霹,但在一般的 JDBC 體系結(jié)構(gòu)由兩層組成:
JDBC API:提供了應(yīng)用程序?qū)?JDBC 的管理連接咪惠。
JDBC Driver API:支持 JDBC 管理到驅(qū)動器連接。
JDBC API 的使用驅(qū)動程序管理器和數(shù)據(jù)庫特定的驅(qū)動程序提供透明的連接到異構(gòu)數(shù)據(jù)庫淋淀。
JDBC 驅(qū)動程序管理器可確保正確的驅(qū)動程序來訪問每個數(shù)據(jù)源遥昧。該驅(qū)動程序管理器能夠支持連接到多個異構(gòu)數(shù)據(jù)庫的多個并發(fā)的驅(qū)動程序。
以下是JDBC結(jié)構(gòu)圖朵纷,它顯示了驅(qū)動程序管理器方面的 JDBC 驅(qū)動程序和 Java 應(yīng)用程序的位置炭臭。
常見的JDBC組件
DriverManager:這個類管理數(shù)據(jù)庫驅(qū)動程序的列表。確定內(nèi)容是否符合從 Java 應(yīng)用程序使用的通信子協(xié)議正確的數(shù)據(jù)庫驅(qū)動程序的連接請求袍辞。識別 JDBC 在一定子協(xié)議的第一個驅(qū)動器將被用來建立數(shù)據(jù)庫連接鞋仍。
Driver: 此接口處理與數(shù)據(jù)庫服務(wù)器通信。很少直接直接使用驅(qū)動程序(Driver)對象搅吁,一般使用 DriverManager 中的對象威创,它用于管理此類型的對象。它也抽象與驅(qū)動程序?qū)ο蠊ぷ飨嚓P(guān)的詳細(xì)信息似芝。
Connection:此接口與接觸數(shù)據(jù)庫的所有方法那婉。連接對象表示通信上下文,即党瓮,與數(shù)據(jù)庫中的所有的通信是通過此唯一的連接對象详炬。
Statement:可以使用這個接口創(chuàng)建的對象的SQL語句提交到數(shù)據(jù)庫。一些派生的接口接受除執(zhí)行存儲過程的參數(shù)寞奸。
ResultSet:這些對象保存從數(shù)據(jù)庫后呛谜,執(zhí)行使用
Statement
對象的 SQL 查詢中檢索數(shù)據(jù)。它作為一個迭代器枪萄,可以通過移動它來檢索下一個數(shù)據(jù)隐岛。SQLException:這個類用于處理發(fā)生在數(shù)據(jù)庫應(yīng)用程序中的任何錯誤。
0x02 創(chuàng)建簡單的一個 JDBC
構(gòu)建 JDBC 應(yīng)用程序涉及以下六個步驟:
導(dǎo)入包:需要包含包含數(shù)據(jù)庫編程所需的 JDBC 類的包瓷翻。大多數(shù)情況下聚凹,使用
import java.sql.*
就足夠了割坠。注冊 JDBC 驅(qū)動程序:需要初始化驅(qū)動程序,以便可以打開與數(shù)據(jù)庫的通信通道妒牙。
打開一個連接:需要使用
DriverManager.getConnection()
方法創(chuàng)建一個Connection
對象彼哼,它表示與數(shù)據(jù)庫的物理連接。執(zhí)行查詢:需要使用類型為
Statement
的對象來構(gòu)建和提交 SQL 語句到數(shù)據(jù)庫湘今。從結(jié)果集中提取數(shù)據(jù):需要使用相應(yīng)的
ResultSet.getXXX()
方法從結(jié)果集中檢索數(shù)據(jù)敢朱。清理環(huán)境:需要明確地關(guān)閉所有數(shù)據(jù)庫資源,而不依賴于 JVM 的垃圾收集摩瞎。
口訣:(賈璉欲執(zhí)事)拴签。
具體實現(xiàn)如下:
1. 導(dǎo)入包
在程序中包含數(shù)據(jù)庫編程所需的 JDBC 類。大多數(shù)情況下旗们,使用 import java.sql.*
就足夠了蚓哩,如下所示。
//STEP 1. Import required packages
import java.sql.*;
2. 注冊JDBC驅(qū)動程序
需要初始化驅(qū)動程序蚪拦,這樣就可以打開與數(shù)據(jù)庫的通信杖剪。以下是代碼片段實現(xiàn)這一目標(biāo)。
//STEP 2: Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
3. 打開一個連接
使用 DriverManager.getConnection()
方法來創(chuàng)建一個 Connection
對象驰贷,它代表一個數(shù)據(jù)庫的物理連接盛嘿,如下所示。
//STEP 3: Open a connection
//Database credentials
static final String USER = "root";
static final String PASS = "pwd123456";
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL, USER, PASS);
4. 執(zhí)行一個查詢
需要使用一個類型為 Statement
或 PreparedStatement
的對象括袒,并提交一個 SQL 語句到數(shù)據(jù)庫執(zhí)行查詢次兆。如下:
//STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
如果要執(zhí)行一個SQL語句:UPDATE
,INSERT
或 DELETE
語句锹锰,那么需要下面的代碼片段:
//STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "DELETE FROM Employees";
ResultSet rs = stmt.executeUpdate(sql);
5. 從結(jié)果集中提取數(shù)據(jù)
這一步中演示如何從數(shù)據(jù)庫中獲取查詢結(jié)果的數(shù)據(jù)芥炭。可以使用適當(dāng)?shù)?ResultSet.getXXX()
方法來檢索的數(shù)據(jù)結(jié)果如下恃慧。
//STEP 5: Extract data from result set
while(rs.next()){
//Retrieve by column name
int id= rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
//Display values
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
6. 清理環(huán)境資源
在使用 JDBC 與數(shù)據(jù)交互操作數(shù)據(jù)庫中的數(shù)據(jù)后园蝠,應(yīng)該明確地關(guān)閉所有的數(shù)據(jù)庫資源以減少資源的浪費,對依賴于 JVM 的垃圾收集如下痢士。
//STEP 6: Clean-up environment
rs.close();
stmt.close();
conn.close();
0x03 JDBC Statements, PreparedStatement 和 CallableStatement 語句
接口 | 推薦使用 |
---|---|
Statement | 用于對數(shù)據(jù)庫進(jìn)行通用訪問彪薛,在運行時使用靜態(tài)SQL語句時很有用。Statement 接口不能接受參數(shù)怠蹂。 |
PreparedStatement | 當(dāng)計劃要多次使用SQL語句時使用善延。PreparedStatement 接口在運行時接受輸入?yún)?shù)。 |
CallableStatement | 當(dāng)想要訪問數(shù)據(jù)庫存儲過程時使用城侧。CallableStatement 接口也可以接受運行時輸入?yún)?shù)易遣。 |
處理 Statement 對象
創(chuàng)建 Statement 對象
在使用 Statement
對象執(zhí)行 SQL 語句之前,需要使用 Connection
對象的 createStatement()
方法創(chuàng)建一個 Statement
對象嫌佑。
在創(chuàng)建 Statement
對象后豆茫,可以使用它來執(zhí)行一個SQL語句侨歉,它有三個執(zhí)行方法可以執(zhí)行。
boolean execute (String SQL)
:如果可以檢索到ResultSet
對象澜薄,則返回一個布爾值true
; 否則返回false
为肮。使用此方法執(zhí)行 SQL DDL 語句或需要使用真正的動態(tài) SQL,可使用于執(zhí)行創(chuàng)建數(shù)據(jù)庫肤京,創(chuàng)建表的 SQL 語句等等。int executeUpdate (String SQL)
:返回受 SQL 語句執(zhí)行影響的行數(shù)茅特。使用此方法執(zhí)行預(yù)期會影響多行的 SQL 語句忘分,例如:INSERT
,UPDATE
或DELETE
語句白修。ResultSet executeQuery(String SQL)
:返回一個ResultSet
對象妒峦。 當(dāng)您希望獲得結(jié)果集時,請使用此方法兵睛,就像使用SELECT
語句一樣肯骇。
關(guān)閉 Statement 對象
操作結(jié)束后,應(yīng)關(guān)閉 Statement
對象祖很。就像關(guān)閉一個 Connection
對象一樣笛丙,以保存數(shù)據(jù)庫資源一樣,由于同樣的原因假颇,還應(yīng)該關(guān)閉 Statement
對象胚鸯。
一個簡單的調(diào)用 close()
方法將執(zhí)行該作業(yè)(工作)。 如果先關(guān)閉 Connection
對象笨鸡,它也會關(guān)閉 Statement
對象姜钳。但是,應(yīng)該始終顯式關(guān)閉 Statement
對象形耗,以確保正確的清理順序哥桥。
Statement stmt = null;
try {
stmt = conn.createStatement( );
// TODO
}
catch (SQLException e) {
// TODO
}
finally {
stmt.close();
}
Statement 對象具體實現(xiàn)
在使用 Statement
對象執(zhí)行 SQL 語句之前,需要使用 Connection
對象的 createStatement()
方法創(chuàng)建一個 Statement
對象激涤,然后調(diào)用上述處理 Statement
類方法提交執(zhí)行 SQL 拟糕。
Statement 操作 DDL
使用 Satemtnt 類創(chuàng)建數(shù)據(jù)庫表。
@Test
public void testDDL() {
String sql = "CREATE TABLE student(id BIGINT AUTO_INCREMENT, name VARCHAR(20), age INT, PRIMARY KEY(id), intro VARCHAR(64))";
Connection connection = null; // 數(shù)據(jù)庫連接對象
Statement statement = null; // 用于執(zhí)行靜態(tài) SQL 語句并返回它所生成結(jié)果的對象
try {
Class.forName("com.mysql.jdbc.Driver"); // 加載注冊驅(qū)動
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db", "root", "root"); // 創(chuàng)建 MySQL 連接對象
statement = connection.createStatement(); // 創(chuàng)建 Statement 對象昔期,用于提交和接收 MySQL
int row = statement.executeUpdate(sql); // 提交 SQL 語句已卸,并接收執(zhí)行結(jié)果
System.out.println("數(shù)據(jù)庫修改行數(shù):" + row); // 執(zhí)行創(chuàng)建表 SQL 影響行數(shù)為 0
} catch (Exception e) { // 釋放資源
e.printStackTrace();
} finally {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Java7 新特性創(chuàng)建數(shù)據(jù)庫表
@Test
public void testDDL_JAVA7() {
String sql = "CREATE TABLE student(id BIGINT AUTO_INCREMENT, name VARCHAR(20), age INT, PRIMARY KEY(id))";
try {
Class.forName("com.mysql.jdbc.Driver");
try (Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db", "root", "root"); Statement st = conn.createStatement();) {
st.executeUpdate(sql);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Statement 操作 DML
@Test
public void testInsert() {
String sql = "INSERT INTO t_student (id, name, age, intro) VALUES (null, '王五', 30, '王五是個好孩子')";
Connection connection = null;
Statement statement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db", "root", "root");
statement = connection.createStatement();
statement.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Statement 操作 DQL
@Test
public void testQuery() {
String sql = "SELECT * FROM t_student";
Connection connection = null;
Statement statement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db", "root", "root");
statement = connection.createStatement();
ResultSet query = statement.executeQuery(sql); // 接收查詢返回的結(jié)果
while (query.next()) { // 遍歷返回結(jié)果集
Long id = query.getLong("id"); // 根據(jù)列名獲取結(jié)果
String name = query.getString("name");
Integer age = query.getInt("age");
String intro = query.getString("intro");
System.out.println(id + " " + name + " " + age + " " + intro);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
PreparedStatement 對象
PreparedStatement
接口擴(kuò)展了 Statement
接口,它添加了比 Statement
對象更好一些優(yōu)點的功能硼一。此語句可以動態(tài)地提供 / 接受參數(shù)累澡。(實例后面提供,將抽取重復(fù)代碼般贼,統(tǒng)一處理結(jié)果集和賦值)
創(chuàng)建 PreparedStatement 對象
JDBC 中的所有參數(shù)都由 ?
符號作為占位符愧哟,這被稱為參數(shù)標(biāo)記奥吩。在執(zhí)行SQL語句之前,必須為每個參數(shù)(占位符)提供值蕊梧。
setXXX()
方法將值綁定到參數(shù)霞赫,其中 XXX
表示要綁定到輸入?yún)?shù)的值的 Java 數(shù)據(jù)類型。 如果忘記提供綁定值肥矢,則將會拋出一個 SQLException
端衰。
每個參數(shù)標(biāo)記是它其順序位置引用。第一個標(biāo)記表示位置 1甘改,下一個位置 2 等等旅东。該方法與 Java 數(shù)組索引不同(它不從 0 開始)。
所有 Statement
對象與數(shù)據(jù)庫交互的方法 execute()
十艾, executeQuery()
和 executeUpdate()
也可以用于 PreparedStatement
對象抵代。但是,這些方法被修改為可以使用輸入?yún)?shù)的 SQL 語句忘嫉。
關(guān)閉PreparedStatement對象
就像關(guān)閉 Statement
對象一樣荤牍,由于同樣的原因 (節(jié)省數(shù)據(jù)庫系統(tǒng)資源),也應(yīng)該關(guān)閉 PreparedStatement
對象庆冕。
簡單的調(diào)用 close()
方法將執(zhí)行關(guān)閉康吵。 如果先關(guān)閉 Connection
對象,它也會關(guān)閉 PreparedStatement
對象愧杯。 但是涎才,應(yīng)該始終顯式關(guān)閉 PreparedStatement
對象,以確保以正確順序清理資源力九。
PreparedStatement pstmt = null;
try {
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
// TODO
}
catch (SQLException e) {
// TODO
}
finally {
pstmt.close();
}
CallableStatement 對象
創(chuàng)建 CallableStatement 對象
類似 Connection
對象創(chuàng)建 Statement
和 PreparedStatement
對象一樣耍铜,它還可以使用同樣的方式創(chuàng)建 CallableStatement
對象,該對象將用于執(zhí)行對數(shù)據(jù)庫存儲過程的調(diào)用跌前。
留坑棕兼。。抵乓。