JDBC (Java Database Connectivity) 是 Java 操作關係型數據庫的標準 API。掌握其四大核心組件——DriverManager、Connection、Statement、ResultSet——的職責邊界與協作方式,是寫出高質量數據訪問代碼的關鍵。本文結合實戰代碼,系統梳理核心概念、工作原理、CRUD 模板與避坑指南。
角色:驅動管理器
職責:
核心方法:getConnection(url, user, pwd)
角色:數據庫連接/會話
職責:
核心方法:createStatement(), prepareStatement()
角色:SQL 執行載體
職責:
核心方法:executeQuery(), executeUpdate()
角色:結果集游標
職責:
核心方法:next(), getString(), getInt()
// 1. 加載驅動 (JDBC 4.0+ 可省略,SPI 自動加載)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立連接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/db?useSSL=false", "root", "123456");
// 3. 創建語句對象
Statement stmt = conn.createStatement();
// 或 PreparedStatement ps = conn.prepareStatement(sql);
// 4. 執行 SQL 處理結果
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// int rows = stmt.executeUpdate("INSERT INTO ...");
// 5. 釋放資源 (反向順序,JDK 7+ 推薦 try-with-resources)
rs.close();
stmt.close();
conn.close();
-- 測試表
CREATE TABLE star_studyone (
id INT PRIMARY KEY,
name VARCHAR(50),
sex VARCHAR(10),
age INT
);
-- Maven 依賴
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
public void insertUser(String name, String sex, int age) {
String sql = "INSERT INTO star_studyone (name, sex, age) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name);
ps.setString(2, sex);
ps.setInt(3, age);
int rows = ps.executeUpdate();
System.out.println(rows > 0 ? "✅ 插入成功" : "❌ 插入失敗");
} catch (SQLException e) {
throw new RuntimeException("插入異常", e);
}
}
public void queryUsers() {
String sql = "SELECT * FROM star_studyone";
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 方式一:按列名獲取 (推薦,不依賴順序)
while (rs.next()) {
System.out.printf("ID: %d, 姓名: %s, 性別: %s, 年齡: %d%n",
rs.getInt("id"),
rs.getString("name"),
rs.getString("sex"),
rs.getInt("age"));
}
// 方式二:通用處理 (配合元數據)
// ResultSetMetaData meta = rs.getMetaData();
// int cols = meta.getColumnCount();
// while (rs.next()) {
// for (int i = 1; i <= cols; i++) {
// System.out.print(rs.getObject(i) + "\t");
// }
// System.out.println();
// }
} catch (SQLException e) {
throw new RuntimeException("查詢異常", e);
}
}
public void updateUserAge(int id, int newAge) {
String sql = "UPDATE star_studyone SET age = ? WHERE id = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, newAge);
ps.setInt(2, id);
int rows = ps.executeUpdate();
System.out.println(rows > 0 ? "✅ 更新成功" : "⚠️ 未找到記錄");
} catch (SQLException e) {
throw new RuntimeException("更新異常", e);
}
}
public void deleteUser(int id) {
String sql = "DELETE FROM star_studyone WHERE id = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
int rows = ps.executeUpdate();
System.out.println(rows > 0 ? "✅ 刪除成功" : "⚠️ 未找到記錄");
} catch (SQLException e) {
throw new RuntimeException("刪除異常", e);
}
}
| 維度 | Statement | PreparedStatement |
|---|---|---|
| SQL 注入防護 | ❌ 無 (拼接字符串) | ✅ 參數化查詢,預編譯 |
| 性能 (重複執行) | 每次編譯 | 預編譯緩存,複用執行計劃 |
| 代碼可讀性 | 拼接繁瑣 | 參數佔位符清晰 |
| 類型安全 | 手動轉換 | setXxx 方法強類型 |
| 適用場景 | 極簡一次性查詢 | ✅ 所有生產代碼 |
// ❌ 危險代碼
String sql = "SELECT * FROM users WHERE name = '" + userInput + "'";
// 輸入: admin' OR '1'='1
// 實際 SQL: SELECT * FROM users WHERE name = 'admin' OR '1'='1' → 繞過驗證
// ✅ 安全代碼
String sql = "SELECT * FROM users WHERE name = ?";
ps.setString(1, userInput); // 參數化,輸入作為字面值處理
public void transfer(String fromAccount, String toAccount, double amount) {
Connection conn = null;
try {
conn = DriverManager.getConnection(URL, USER, PASS);
conn.setAutoCommit(false); // 開啟事務
// 扣款
updateBalance(conn, fromAccount, -amount);
// 入賬
updateBalance(conn, toAccount, amount);
conn.commit(); // 提交事務
System.out.println("✅ 轉賬成功");
} catch (SQLException e) {
if (conn != null) {
try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); }
}
throw new RuntimeException("❌ 轉賬失敗,已回滾", e);
} finally {
if (conn != null) {
try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
private void updateBalance(Connection conn, String account, double delta) throws SQLException {
String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setDouble(1, delta);
ps.setString(2, account);
ps.executeUpdate();
}
}
| 錯誤現象 | 根本原因 | 修正方案 |
|---|---|---|
| ClassNotFoundException | 驅動包未在 classpath | Maven 引入正確版本 mysql-connector-java |
| Communications link failure | URL/主機/端口錯誤、防火牆 | 檢查 jdbc:mysql://ip:port/db、telnet 驗證端口 |
| Public Key Retrieval is not allowed | MySQL 8.0+ 默認需 SSL | URL 添加 allowPublicKeyRetrieval=true&useSSL=false |
| 中文亂碼 | 字符集不一致 | URL characterEncoding=utf8、表 charset=utf8mb4、JVM -Dfile.encoding=UTF-8 |
| ResultSet 獲取數據報錯 | 列索引從 1 開始、列名大小寫 | 使用列名 rs.getString("name")、檢查別名 |
| 資源洩漏 (連接數耗盡) | 未關閉 Connection/Statement/ResultSet | 統一使用 try-with-resources、配置連接池 |
Class.forNameJDBC 雖然是底層 API,但其設計哲學 (接口隔離、驅動解耦、資源管理) 貫穿整個 Java 數據訪問生態。深入理解這四大接口的協作機制,不僅能寫出正確的數據庫代碼,更能看透 MyBatis、JPA、Spring JDBC 等上層框架的底層原理。