雖然現代企業開發普遍採用 Hibernate、MyBatis 等 ORM 框架簡化資料存取,但 JDBC(Java Database Connectivity)作為 Java 操作關聯式資料庫的基石,深入理解其運作機制對於排查效能瓶頸、掌握底層資料流轉至關重要。本文將系統梳理 JDBC 核心流程、核心物件、進階特性與最佳實踐,助你建立扎實的資料庫程式設計基礎。
JDBC 操作資料庫遵循標準化的四步驟:
Class.forName() 載入特定資料庫的 JDBC 驅動類別。DriverManager.getConnection(url, user, password) 取得 Connection 物件。Statement、PreparedStatement 或 CallableStatement 發送 CRUD 操作。ResultSet 遍歷查詢結果,或獲取更新影響的行數。最後務必在 finally 區塊中關閉 ResultSet、Statement、Connection,避免資源洩漏。
| 物件 | 用途 |
|---|---|
DriverManager | 管理驅動程式,建立資料庫連線 |
Connection | 代表與資料庫的會話,支援事務控制 |
Statement | 執行靜態 SQL 語句 |
PreparedStatement | 預編譯 SQL,防注入、提升效能 |
CallableStatement | 呼叫預存程序 |
ResultSet | 封裝查詢結果集,支援游標移動 |
Savepoint | 事務中設定回滾點 |
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, "root", "password");
將驅動完整類名寫入 META-INF/services/java.sql.Driver 檔案,置於 classpath 中,即可省略 Class.forName()。切換資料庫時僅需修改此檔案內容與對應 JAR 包。
以下以 MySQL 為例,展示增刪改查完整流程:
try (Connection conn = DriverManager.getConnection(url, user, pwd);
Statement stmt = conn.createStatement()) {
// 新增
stmt.executeUpdate("INSERT INTO user(id,name) VALUES(1,'張三')");
// 查詢
try (ResultSet rs = stmt.executeQuery("SELECT id,name FROM user")) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " - " + rs.getString("name"));
}
}
// 修改
stmt.executeUpdate("UPDATE user SET name='李四' WHERE id=1");
// 刪除
stmt.executeUpdate("DELETE FROM user WHERE id=1");
}
CallableStatement cstmt = conn.prepareCall("{call GetUser()}");
ResultSet rs = cstmt.executeQuery();
// IN 參數
cstmt = conn.prepareCall("{call GetUserByID(?)}");
cstmt.setInt(1, 10);
// OUT 參數
cstmt.registerOutParameter(2, Types.VARCHAR);
cstmt.execute();
String outVal = cstmt.getString(2);
DatabaseMetaData dbmd = conn.getMetaData();
System.out.println(dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion());
ResultSet tables = dbmd.getTables(null, null, "%", new String[]{"TABLE"});
while (tables.next()) {
System.out.println(tables.getString("TABLE_NAME"));
}
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
System.out.println(rsmd.getColumnName(i) + " : " + rsmd.getColumnTypeName(i));
}
建立 Statement 時指定型別與並發模式:
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
支援操作:
rs.last() / rs.first() / rs.absolute(n) 移動游標rs.moveToInsertRow() 移至插入行rs.updateXxx(col, val) 修改欄位值rs.insertRow() / rs.updateRow() / rs.deleteRow() 提交變更SQL 模板預編譯,參數化查詢防注入,資料庫端緩存執行計畫:
PreparedStatement pst = conn.prepareStatement("INSERT INTO user(id,name) VALUES(?,?)");
pst.setInt(1, 100);
pst.setString(2, "王五");
pst.executeUpdate();
Statement stmt = conn.createStatement();
stmt.addBatch("INSERT INTO user VALUES(1,'A')");
stmt.addBatch("INSERT INTO user VALUES(2,'B')");
stmt.executeBatch();
PreparedStatement pst = conn.prepareStatement("INSERT INTO user(id,name) VALUES(?,?)");
for (int i = 0; i < 1000; i++) {
pst.setInt(1, i);
pst.setString(2, "User" + i);
pst.addBatch();
}
pst.executeBatch();
conn.setAutoCommit(false);
try {
stmt.executeUpdate("INSERT INTO account VALUES(1,100)");
stmt.executeUpdate("UPDATE account SET balance=balance-50 WHERE id=1");
conn.commit();
} catch (Exception e) {
conn.rollback();
} finally {
conn.setAutoCommit(true);
}
Savepoint sp = conn.setSavepoint("SP1");
stmt.executeUpdate("INSERT INTO log VALUES(1,'step1')");
stmt.executeUpdate("INSERT INTO log VALUES(2,'step2')");
conn.rollback(sp); // 僅回滾到 SP1 之後的操作
conn.commit();
PreparedStatement 防止 SQL 注入並複用執行計畫。try-with-resources 或 finally 中關閉。掌握 JDBC 核心機制,不僅能在無框架環境下游刃有餘,更能在使用 ORM 時深入理解其底層行為,快速定位效能與資料一致性問題。