以下內容全面詳盡地梳理了 JDBC (Java Database Connectivity)的核心知識點,并在關鍵環節配以示例代碼。若要快速定位,可先查看下方結構:
- JDBC 概覽
- 驅動加載與注冊
- 獲取數據庫連接
- 執行 SQL(Statement、PreparedStatement、CallableStatement)
- 處理結果集(ResultSet)
- 批量操作與批處理
- 事務管理
- 元數據操作(DatabaseMetaData、ResultSetMetaData)
- 異常處理
- 連接池與 DataSource
- 最佳實踐與性能優化
1. JDBC 概覽
- 用途:提供 Java 程序訪問各種關系型數據庫(MySQL、Oracle、PostgreSQL 等)的統一 API。
- 核心接口包:
java.sql.*
- 主要角色:
- DriverManager/Driver:驅動注冊、連接分發
- Connection:管理會話、事務
- Statement/PreparedStatement/CallableStatement:執行 SQL
- ResultSet:承載查詢結果
- DataSource:替代 DriverManager 的連接獲取方式(可集成連接池)
執行過程可總結為:
- 驅動加載(現在可以省略)
- 獲取數據庫連接
- 定義要執行的sql語句
- 獲取執行對象
- 執行對象執行sql語句,獲取返回結果
- 后續處理
2. 驅動加載與注冊
2.1 自動注冊(JDBC 4.0+)
只要在 classpath 中包含相應數據庫的 JDBC 驅動(如 mysql-connector-java.jar
),Driver 會通過 SPI 自動注冊,無需顯式調用。
// 不需要 Class.forName(...),直接拿連接即可
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "root", "password");
2.2 手動加載(向后兼容)
Class.forName("com.mysql.cj.jdbc.Driver");
// 或者老版本
// Class.forName("com.mysql.jdbc.Driver");
3. 獲取數據庫連接
String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "password";try (Connection conn = DriverManager.getConnection(url, user, password)) {// 使用 conn
} catch (SQLException e) {e.printStackTrace();
}
- 常用參數:
useSSL=false
:禁用 SSLserverTimezone=UTC
:指定時區autoReconnect=true
:自動重連
當然可以,以下是重新整理并擴充后的第4部分內容,加入了 SQL 注入的講解,并對 Statement
的兩個方法做了深入分析,保持原有結構和風格:
4. 獲取執行 SQL 對象,Cnn 獲取
在 JDBC 中,獲取數據庫連接后,我們需要通過執行 SQL 語句與數據庫交互,這一過程通常通過以下三種執行對象實現:
Statement
:用于執行靜態 SQL 語句(不帶參數),簡單但易受 SQL 注入攻擊;PreparedStatement
:預編譯 SQL 語句,支持參數綁定,性能更優、安全性更高;
Statement 的核心方法:
int executeUpdate(String sql)
- 執行 DML(INSERT、UPDATE、DELETE) 或 DDL(CREATE、DROP)。
- 返回值為 受影響的行數,若為 DDL,通常返回 0(不代表失敗)。
ResultSet executeQuery(String sql)
- 執行 DQL(SELECT)語句,返回查詢結果集
ResultSet
。
關于 SQL 注入(SQL Injection)
SQL 注入是一種嚴重的安全漏洞,攻擊者可以通過拼接惡意 SQL 來操控數據庫。例如:
String name = "' OR '1'='1";
String sql = "SELECT * FROM user WHERE name = '" + name + "'";
構造出的 SQL 實際是:
SELECT * FROM user WHERE name = '' OR '1'='1'
where后面的結果將永遠是真的,所以就可以永遠的查詢出來.
將返回所有用戶數據,甚至可用于刪除表結構,極其危險!
4.1 Statement(不預編譯,易受 SQL 注入)
String name = "張三";
String sql = "SELECT id, name FROM user WHERE name = '" + name + "'";
try (Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql)
) {while (rs.next()) {System.out.println(rs.getInt("id") + ": " + rs.getString("name"));}
}
優點:簡單,適合一次性、非常簡單的 SQL;
缺點:拼接字符串,極易受 SQL 注入攻擊,不能復用或預編譯,性能差。
4.2 PreparedStatement(預編譯,防注入、性能更優)
String sql = "SELECT id, name FROM user WHERE name = ? AND age > ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setString(1, "張三");pstmt.setInt(2, 18);try (ResultSet rs = pstmt.executeQuery()) {while (rs.next()) {System.out.println(rs.getInt("id") + ": " + rs.getString("name"));}}
}
優點:
- 使用 占位符 ? 實現參數綁定,不拼接字符串,有效防止 SQL 注入
- SQL 會被 預編譯并緩存,適合多次執行,提高效率
- 自動轉義字符串,避免因特殊字符出錯
缺點:相比 Statement 書寫稍復雜,不適合拼接動態結構的 SQL(如條件列名)。
Statement vs PreparedStatement 詳細對比
特性 | Statement | PreparedStatement |
---|---|---|
SQL 預編譯 | 否 | 是(可緩存執行計劃) |
防 SQL 注入能力 | 差(需要手動拼接字符串) | 強(使用參數綁定) |
性能(重復執行場景) | 差(每次都解析) | 優(執行計劃可復用) |
可讀性與維護性 | 差(大量拼接容易混亂) | 高(結構清晰,安全) |
支持占位符 ? | 否 | 是 |
使用場景 | 簡單、臨時查詢 | 正式項目、復雜參數、安全性要求高場合 |
5. 處理結果集(ResultSet)
- 指針導航:
.next()
,.first()
,.last()
,.absolute(n)
(需指定 TYPE_SCROLL) - 獲取列值:
getInt()
,getString()
,getDate()
等 - 按列名或列索引
- 類型轉換注意:避免隱式類型轉換帶來的性能/精度問題
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {int id = rs.getInt(1);String name = rs.getString("name");Date dob = rs.getDate("dob");// ...
}
6. 批量操作與批處理
批量執行可顯著提升大量插入/更新的性能。
String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {conn.setAutoCommit(false); // 關閉自動提交for (int i = 1; i <= 1000; i++) {pstmt.setString(1, "User" + i);pstmt.setInt(2, 20 + (i % 30));pstmt.addBatch();if (i % 200 == 0) {pstmt.executeBatch();conn.commit(); // 分批提交}}// 提交剩余pstmt.executeBatch();conn.commit();
}
7. 事務管理Cnn對象負責管理
try {conn.setAutoCommit(false); // 開啟事務// 操作1// 操作2// ...conn.commit(); // 提交事務
} catch (SQLException e) {conn.rollback(); // 回滾事務
} finally {conn.setAutoCommit(true); // 恢復默認
}
- 隔離級別:
conn.setTransactionIsolation(...)
- 保存點:
Savepoint sp = conn.setSavepoint("sp1"); conn.rollback(sp);
8. 元數據操作
8.1 DatabaseMetaData
DatabaseMetaData dbMeta = conn.getMetaData();
System.out.println("Database product: " + dbMeta.getDatabaseProductName());
System.out.println("Driver version: " + dbMeta.getDriverVersion());
8.2 ResultSetMetaData
ResultSetMetaData rsMeta = rs.getMetaData();
int columnCount = rsMeta.getColumnCount();
for (int i = 1; i <= columnCount; i++) {System.out.println("Column " + i + ": " + rsMeta.getColumnName(i)+ " (" + rsMeta.getColumnTypeName(i) + ")");
}
9. 異常處理
- SQLException:
.getErrorCode()
:數據庫錯誤碼.getSQLState()
:標準 SQLState
- 資源釋放:強烈推薦使用
try-with-resources
自動關閉Connection
、Statement
、ResultSet
try (Connection conn = …;PreparedStatement pstmt = conn.prepareStatement(...);ResultSet rs = pstmt.executeQuery();
) {// ...
} catch (SQLException e) {System.err.println("Error Code: " + e.getErrorCode());System.err.println("SQL State : " + e.getSQLState());e.printStackTrace();
}
10. 連接池與 DataSource
10.1 為什么使用連接池
- 連接創建開銷大
- 重復使用可提升性能
10.2 常見連接池
- HikariCP(高性能)
- Apache DBCP
- C3P0
10.3 DataSource 示例(HikariCP)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(10);DataSource ds = new HikariDataSource(config);// 獲取連接
try (Connection conn = ds.getConnection()) {// ...
}
11. 最佳實踐與性能優化
- 使用 PreparedStatement 防止 SQL 注入、提升性能。
- 合理設置批量大小,避免長事務占用資源。
- 關閉不必要的自動提交,批量操作時手動管理事務。
- 使用連接池,避免頻繁創建銷毀連接。
- 定期監控慢查詢,并為頻繁訪問的字段添加索引。
- 盡量只查詢所需列,避免
SELECT *
。 - 及時關閉資源,防止連接泄漏(
try-with-resources
)。
IDEA調試總結:
更通俗、貼近實際編程操作的語言來重新解釋 IntelliJ IDEA 中調試時常用的快捷鍵,避免術語,讓你一看就懂:
IDEA 調試快捷鍵(簡單易懂版)
功能 | Windows / Linux | macOS | 說明(通俗解釋) |
---|---|---|---|
啟動調試 | Shift + F9 | Control + D | 像運行程序一樣運行,但可以中途停下來看代碼狀態 |
停止調試 | Ctrl + F2 | Command + F2 | 停掉整個程序 |
一步一步執行代碼(當前行) | F8 | F8 | 當前行執行完再停在下一行(如果是循環,就跳下一次) |
進入方法體中去看具體執行 | F7 | F7 | 如果當前行是一個函數調用,按它會跳進去函數里看細節 |
跳出當前方法返回上一層 | Shift + F8 | Shift + F8 | 如果你已經進了方法里,按這個會回到調用這個方法的地方 |
執行到光標處停下 | Alt + F9 | Option + F9 | 讓程序一直跑到你當前鼠標點的位置再停下(省得一直按F8) |
繼續往下執行程序 | F9 | Command + Option + R 或 F9 | 程序繼續往下跑,直到遇到下一個斷點 |
設置或取消斷點 | Ctrl + F8 | Command + F8 | 在代碼左邊點一下,紅點就是斷點,程序會在這里暫停 |
常用調試輔助操作
功能 | Windows / Linux | macOS | 說明 |
---|---|---|---|
查看某個變量的值 | 鼠標懸停 | 鼠標懸停 | 把鼠標放在變量上面,就會顯示它當前的值 |
手動輸入表達式查看結果 | Alt + F8 | Option + F8 | 彈出一個窗口,你可以輸入變量或表達式看結果,比如 a + b |
查看所有斷點 | Ctrl + Shift + F8 | Command + Shift + F8 | 可以集中管理所有斷點 |
快速查看變量內容 | Ctrl + Shift + I | Command + Shift + I | 不用跳轉,臨時查看變量內容 |
舉個簡單例子:
public class Demo {public static void main(String[] args) {int a = 10;int b = 20;int c = add(a, b); // 👉 在這里打斷點System.out.println("結果是:" + c);}public static int add(int x, int y) {return x + y;}
}
如果你在 int c = add(a, b);
那一行打斷點:
- 按
Shift + F9
開始調試; - 程序會停在
add(a, b)
那一行; - 按
F8
,它會直接執行完這一行然后到下一行; - 按
F7
,它會跳進add()
方法里面; - 在方法中可以繼續按
F8
看一步步怎么返回結果; - 再按
Shift + F8
就可以返回到 main() 繼續調試; - 最后按
F9
可以直接讓程序跑完。
如果你還不熟練,可以先只記住這兩個:
- 👉
F8
:一步一步往下執行; - 👉
F7
:進入函數體里看看里面是怎么執行的。