JDBC (Java Database Connectivity) 是 Java 操作關聯式資料庫的統一 API 標準。掌握其核心介面、標準流程、事務邊界控制、批次處理與元數據編程,是構建高效、安全資料存取層的必修課。
一、 核心 API 體系速覽
| 介面/類別 | 核心職責 | 關鍵方法 |
DriverManager | 驅動管理、獲取連線 | getConnection(url, user, pwd) |
Connection | 資料庫會話、事務控制、Statement 工廠 | createStatement(), prepareStatement(), setAutoCommit(), commit(), rollback(), setSavepoint() |
Statement | 執行靜態 SQL | executeQuery(), executeUpdate(), execute() |
PreparedStatement | 預編譯參數化 SQL (防注入、效能優) | setXxx(index, val), executeQuery(), executeUpdate() |
CallableStatement | 呼叫預存程序 | registerOutParameter(), getXxx() |
ResultSet | 查詢結果集游標 | next(), getXxx(), updateXxx(), absolute() |
ResultSetMetaData | 結果集結構描述 | getColumnCount(), getColumnName(), getColumnType() |
DatabaseMetaData | 資料庫產品特性描述 | supportsBatchUpdates(), getTables(), getColumns() |
二、 標準編程六步曲
// 1. 載入驅動 (JDBC 4.0+ SPI 自動載入,可省略)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 獲取連線
try (Connection conn = DriverManager.getConnection(url, user, pwd)) {
// 3. 建立 Statement (優先 PreparedStatement)
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO users(username, email) VALUES (?, ?)")) {
// 4. 綁定參數、執行
ps.setString(1, "alice");
ps.setString(2, "alice@example.com");
int rows = ps.executeUpdate(); // 回傳影響列數
// 5. 處理結果集 (查詢時)
// try (ResultSet rs = ps.executeQuery()) { while(rs.next()) { ... } }
}
} // try-with-resources 自動關閉資源、釋放連線回池
三、 三大 Statement 對比與選型
| 特性 | Statement | PreparedStatement | CallableStatement |
| SQL 編譯 | 每次執行編譯 | 預編譯一次,多次執行 | 預編譯 CALL 語法 |
| 參數綁定 | 字串拼接 (風險高) | setXxx() 型別安全 | setXxx() + registerOutParameter() |
| 防 SQL 注入 | 無 | 強 (參數與代碼分離) | 強 |
| 效能 | 低 | 高 (計畫快取) | 高 |
| 適用場景 | 極簡測試、DDL | 95% 以上業務 SQL | 預存程序、複雜邏輯下推 |
鐵律:業務代碼 永遠使用 PreparedStatement,拒絕字串拼接。
四、 ResultSet 進階操作
1. 可捲動、可更新結果集
// 建立支援雙向游標、可更新的 ResultSet
try (Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, // 可捲動,不敏感底層變更
ResultSet.CONCUR_UPDATABLE)) { // 可更新
try (ResultSet rs = stmt.executeQuery("SELECT id, name FROM users")) {
rs.last(); // 移至最後一行
rs.previous(); // 移至上一行
rs.absolute(5); // 移至第 5 行
// 更新當前行
rs.updateString("name", "newName");
rs.updateRow(); // 同步回資料庫
// 插入新行
rs.moveToInsertRow();
rs.updateString("name", "inserted");
rs.insertRow();
rs.moveToCurrentRow();
}
}
2. Blob/CLOB 大物件處理
// 寫入圖片
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO docs(file) VALUES (?)");
FileInputStream fis = new FileInputStream("logo.png")) {
ps.setBinaryStream(1, fis, fis.available());
ps.executeUpdate();
}
// 讀取圖片
try (ResultSet rs = stmt.executeQuery("SELECT file FROM docs WHERE id=1")) {
if (rs.next()) {
try (InputStream is = rs.getBinaryStream("file");
FileOutputStream fos = new FileOutputStream("out.png")) {
byte[] buf = new byte[4096];
for (int len; (len = is.read(buf)) > 0; ) fos.write(buf, 0, len);
}
}
}
3. ResultSetMetaData 動態獲取結構
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
for (int i = 1; i <= cols; i++) {
System.out.printf("欄位 %d: %s (%s)%n",
i, meta.getColumnName(i), meta.getColumnTypeName(i));
}
五、 事務管理:ACID 與 JDBC 控制
1. 事務邊界控制
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 開啟事務
// 業務操作 A
updateBalance(conn, from, -100);
// 業務操作 B
updateBalance(conn, to, +100);
conn.commit(); // 全部成功提交
} catch (SQLException e) {
if (conn != null) conn.rollback(); // 任一失敗回滾
throw e;
} finally {
if (conn != null) {
conn.setAutoCommit(true); // 歸還連線池前恢復預設
conn.close();
}
}
2. 保存點:巢狀事務模擬
Savepoint sp1 = conn.setSavepoint("BeforeRiskyOp");
try { riskyOperation(); }
catch (Exception e) { conn.rollback(sp1); } // 僅回滾到保存點
// 繼續其他操作...
conn.releaseSavepoint(sp1);
3. 隔離級別設定
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// 可選:READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
六、 批次更新:大量寫入加速器
public int[] batchInsert(List users) {
String sql = "INSERT INTO users(name, email) VALUES (?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
conn.setAutoCommit(false); // 單一事務包裹整批
for (User u : users) {
ps.setString(1, u.getName());
ps.setString(2, u.getEmail());
ps.addBatch();
}
int[] results = ps.executeBatch(); // 一次性送往資料庫
conn.commit();
return results; // 每條 SQL 影響列數陣列
} catch (BatchUpdateException e) {
// 部分失敗處理
int[] ok = e.getUpdateCounts();
// ...
throw e;
}
}
批次更新黃金法則:關閉自動提交 → addBatch() 累積 → executeBatch() → commit() → 異常時 rollback()。
七、 DatabaseMetaData:跨資料庫適配利器
DatabaseMetaData dbMeta = conn.getMetaData();
// 1. 判斷資料庫能力
boolean batch = dbMeta.supportsBatchUpdates();
boolean savepoint = dbMeta.supportsSavepoints();
// 2. 獲取所有表資訊
try (ResultSet tables = dbMeta.getTables(null, "public", "%", new String[]{"TABLE"})) {
while (tables.next()) {
System.out.println(tables.getString("TABLE_NAME"));
}
}
// 3. 獲取特定表欄位結構
try (ResultSet cols = dbMeta.getColumns(null, "public", "users", "%")) {
while (cols.next()) {
System.out.printf("%s %s nullable=%s%n",
cols.getString("COLUMN_NAME"),
cols.getString("TYPE_NAME"),
cols.getString("IS_NULLABLE"));
}
}
八、 實戰最佳實踐清單
- 連線池必備:HikariCP / Druid,生產環境禁用
DriverManager 直連。
- PreparedStatement 快取:連線池層面開啟
prepStmtCacheSize 避免重複編譯。
- 資源自動管理:全面使用
try-with-resources (JDK 7+)。
- 大數據分頁:避免
LIMIT offset, size 深分頁,改用游標/Keyset 分頁。
- 參數型別匹配:
setLong() 對應 BIGINT,避免隱式轉換導致索引失效。
- 異常轉換:捕獲
SQLException 包裝為業務異常,保留 SQLState、ErrorCode、原始 SQL 便於排查。