前言
JDBC (Java Database Connectivity) 是 Java 存取關聯式資料庫的標準 API。本文從基礎步驟進階到生產級最佳實踐,涵蓋驅動載入、連線管理、SQL 執行、結果處理、資源釋放、連線池、事務與防注入。
JDBC 核心 API 概覽
| 介面/類別 | 套件 | 職責 |
| DriverManager | java.sql | 管理 JDBC 驅動、建立連線 (簡單應用) |
| DataSource | javax.sql | 連線池介面,生產環境標準取得連線方式 |
| Connection | java.sql | 資料庫會話、事務控制、建立 Statement |
| Statement | java.sql | 執行靜態 SQL |
| PreparedStatement | java.sql | 預編譯 SQL、參數綁定、**防 SQL 注入** |
| CallableStatement | java.sql | 呼叫預存程序 |
| ResultSet | java.sql | 查詢結果集游標 |
| SQLException | java.sql | 資料庫操作異常 |
基礎六步驟 (DriverManager 版)
public class JdbcBasicDemo {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/pet?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Taipei";
String user = "root";
String password = "123456";
// 1. 載入驅動 (JDBC 4.0+ SPI 機制可省略,但建議保留相容)
try { Class.forName("com.mysql.cj.jdbc.Driver"); }
catch (ClassNotFoundException e) { throw new RuntimeException("找不到驅動", e); }
// 2-6. try-with-resources 自動關閉資源 (JDK 7+)
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement("SELECT COUNT(1) AS cnt FROM users WHERE username=? AND userpwd=?")) {
// 3. 設定參數
ps.setString(1, "admin");
ps.setString(2, "123456");
// 4. 執行查詢
try (ResultSet rs = ps.executeQuery()) {
// 5. 處理結果
if (rs.next()) {
int count = rs.getInt("cnt");
System.out.println(count == 1 ? "登入成功" : "登入失敗");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
// 6. 連線自動關閉 (try-with-resources)
}
}
進階:連線池 (DataSource) - 生產環境必備
HikariCP (Spring Boot 2.x+ 預設、效能最強)
// Maven
com.zaxxer
HikariCP
5.0.1
// 配置 (application.yml)
spring:
datasource:
url: jdbc:mysql://localhost:3306/pet?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Taipei
username: root
password: 123456
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
手動建立 HikariDataSource (非 Spring 環境)
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.setMaximumPoolSize(20);
DataSource ds = new HikariDataSource(config);
// 使用
try (Connection conn = ds.getConnection(); ...) { ... }
關鍵最佳實踐總結
| 實踐 | 錯誤寫法 | 正確寫法 | 理由 |
| 參數化查詢 | `"SELECT * FROM t WHERE name='" + name + "'"` | `PreparedStatement` + `setString` | 防 SQL 注入、預編譯重用執行計畫 |
| 資源釋放 | 手動 close、漏關閉 | try-with-resources (JDK 7+) | 保證釋放、程式碼簡潔 |
| 事務控制 | 自動提交、業務中間拋異常不回滾 | `conn.setAutoCommit(false)` + `commit`/`rollback` | 保證 ACID |
| 批次處理 | 迴圈逐筆 executeUpdate | `addBatch()` + `executeBatch()` | 減少網路往返、提升 10x+ 效能 |
| 大資料查詢 | 一次性載入所有 ResultSet | `setFetchSize(1000)` + 游標逐行處理 | 避免 OOM、記憶體恆定 |
| 連線獲取 | 每次 `DriverManager.getConnection` | 連線池 `DataSource.getConnection()` | 重用連線、控制併發、監控指標 |
事務控制範例
public void transfer(String from, String to, double amount) throws SQLException {
try (Connection conn = ds.getConnection()) {
conn.setAutoCommit(false); // 開啟事務
try (PreparedStatement dec = conn.prepareStatement("UPDATE account SET balance=balance-? WHERE user=?");
PreparedStatement inc = conn.prepareStatement("UPDATE account SET balance=balance+? WHERE user=?")) {
dec.setDouble(1, amount); dec.setString(2, from);
inc.setDouble(1, amount); inc.setString(2, to);
int decRows = dec.executeUpdate();
int incRows = inc.executeUpdate();
if (decRows == 1 && incRows == 1) {
conn.commit(); // 提交
} else {
conn.rollback(); // 回滾
throw new SQLException("轉帳失敗:扣款或入帳行數異常");
}
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
}
常見錯誤與排查
| 錯誤 | 原因 | 解決 |
| ClassNotFoundException: Driver | JDBC 驅動 JAR 未在 classpath | Maven 引入對應驅動 (mysql-connector-j, postgresql, ojdbc11 等) |
| Communications link failure | 網路不通、資料庫未啟動、防火牆、URL 錯誤 | 檢查 host/port、ping、telnet、資料庫狀態、防火牆規則 |
| Public Key Retrieval is not allowed | MySQL 8.0+ 預設需 SSL | URL 加 `allowPublicKeyRetrieval=true&useSSL=false` (開發環境) |
| Connection refused / Timeout | 連線池耗盡、資料庫 max_connections 達上限 | 調大連線池、檢查洩漏、優化慢查詢、啟用連線洩漏偵測 |
| ResultSet is closed | Statement/Connection 關閉後仍操作 ResultSet | 在 try-with-resources 區塊內處理完畢 |
結語
JDBC 雖是底層 API,但**連線池 + PreparedStatement + try-with-resources + 事務邊界** 四大核心實踐構成了穩健資料存取的基石。現代專案建議直接使用 **Spring JDBC (JdbcTemplate/NamedParameterJdbcTemplate)** 或 **MyBatis/JOOQ** 進一步簡化樣板代碼,但理解 JDBC 底層機制對排查疑難雜症、效能調優至關重要。