首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JDBC 核心教學:Statement、PreparedStatement、CallableStatement 三大介面詳解

     編輯:Java 相關知識

Statement 體系繼承關係

Statement (介面)
    ↑ 繼承
PreparedStatement (介面) — 預編譯 SQL、參數化查詢、防 SQL 注入
    ↑ 繼承
CallableStatement (介面) — 預存程序呼叫、OUT/INOUT 參數支援

建立與執行基礎流程

Connection conn = DriverManager.getConnection(url, user, pass);

// 1. Statement:簡單靜態 SQL
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE active = 1");
int rows = stmt.executeUpdate("UPDATE users SET last_login = NOW() WHERE id = 1");

// 2. PreparedStatement:參數化 SQL (核心推薦)
PreparedStatement pstmt = conn.prepareStatement(
    "INSERT INTO users (username, email, created_at) VALUES (?, ?, ?)");
pstmt.setString(1, "john_doe");
pstmt.setString(2, "john@example.com");
pstmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
int affected = pstmt.executeUpdate();

// 3. CallableStatement:預存程序
CallableStatement cstmt = conn.prepareCall("{call get_user_stats(?, ?)}");
cstmt.setInt(1, userId);           // IN 參數
cstmt.registerOutParameter(2, Types.INTEGER); // OUT 參數
cstmt.execute();
int totalOrders = cstmt.getInt(2);

三大執行方法選擇指南

方法回傳值適用 SQLStatement 版本參數PreparedStatement/CallableStatement 版本參數
executeQuery()ResultSetSELECT、可返回結果集的預存程序SQL 字串無參數 (SQL 已預編譯)
executeUpdate()int (影響筆數)INSERT/UPDATE/DELETE、DDL (CREATE/DROP)SQL 字串無參數
execute()boolean (true=ResultSet, false=update count)未知類型、多結果集、複雜預存程序SQL 字串無參數

PreparedStatement 核心優勢

  1. 防 SQL 注入:參數作為資料傳遞,不會被解析為 SQL 片段。
  2. 效能優化:資料庫端解析、編譯、優化一次,多次執行復用執行計畫。
  3. 型別安全:setXxx(index, value) 編譯期檢查型別。
  4. 批次處理:addBatch() + executeBatch() 大幅提升大量寫入效能。

批次插入範例

conn.setAutoCommit(false); // 手動控制事務
try (PreparedStatement pstmt = conn.prepareStatement(
        "INSERT INTO logs (level, message, created_at) VALUES (?, ?, ?)")) {

    for (LogEntry entry : largeBatch) {
        pstmt.setString(1, entry.getLevel());
        pstmt.setString(2, entry.getMessage());
        pstmt.setTimestamp(3, new Timestamp(entry.getTimestamp()));
        pstmt.addBatch();
    }
    int[] results = pstmt.executeBatch(); // 每個元素對應一條 SQL 影響筆數
    conn.commit();
} catch (BatchUpdateException e) {
    conn.rollback();
    int[] updateCounts = e.getUpdateCounts(); // 部分成功資訊
}

SQL Escape 語法 (跨資料庫可攜性)

格式:{keyword ...},驅動程式轉換為目標資料庫原生語法。

關鍵字用途範例
escapeLIKE 逸出字元{escape '\'} → WHERE name LIKE '\_%' {escape '\'}
fn標量函數{fn concat('Hello', 'World')}, {fn now()}
d日期常值{d '2024-12-31'}
t時間常值{t '23:59:59'}
ts時間戳常值{ts '2024-12-31 23:59:59.999'}
call預存程序呼叫{call proc(?, ?)}, {? = call func(?)}
oj外部聯結{oj table1 LEFT OUTER JOIN table2 ON table1.id = table2.id}

注意:Statement.setEscapeProcessing(false) 可關閉掃描以提升效能,但 PreparedStatement 不支援 (預編譯階段已處理)。

CallableStatement 參數模式完整指南

IN 參數

cstmt.setString(1, "input_value"); // 同 PreparedStatement

OUT 參數 (必須先註冊 JDBC 型別)

cstmt.registerOutParameter(1, Types.VARCHAR); // 或 Types.INTEGER 等
cstmt.execute();
String result = cstmt.getString(1); // 取得 OUT 參數值

INOUT 參數 (雙向)

cstmt.setInt(1, 100); // 設定 IN 值
cstmt.registerOutParameter(1, Types.INTEGER); // 註冊 OUT 型別 (必須相容)
cstmt.execute();
int newValue = cstmt.getInt(1); // 取得 OUT 值

關鍵順序規則:先處理結果集,再取 OUT 參數

// 預存程序返回 ResultSet + OUT 參數
cstmt.execute(); // 或 executeQuery()

// 1. 先處理所有 ResultSet
ResultSet rs = cstmt.getResultSet();
while (rs != null) {
    while (rs.next()) { /* ... */ }
    rs.close();
    if (!cstmt.getMoreResults()) break;
    rs = cstmt.getResultSet();
}

// 2. 再取 OUT 參數
int outParam = cstmt.getInt(1);

Statement 生命週期與事務邊界

  • Auto-commit 模式 (預設):
    • executeQuery:讀完所有列 → 語句完成 → 自動提交。
    • executeUpdate:執行完成 → 語句完成 → 自動提交。
    • execute:所有結果集/更新計數處理完 → 語句完成 → 自動提交。
  • 手動事務控制:
    conn.setAutoCommit(false);
    try {
        stmt.executeUpdate(sql1);
        pstmt.executeUpdate(sql2);
        conn.commit();
    } catch (SQLException e) {
        conn.rollback();
        throw e;
    } finally {
        conn.setAutoCommit(true); // 歸還連線池前恢復預設
    }
  • 預存程序事務行為:視資料庫而定,Oracle/PostgreSQL 預設在呼叫者事務中,SQL Server 可自主提交。建議在 Java 層統一控制事務邊界。

關閉順序與資源洩漏防護

// 標準模式 (JDK 7+)
try (Connection conn = ds.getConnection();
     PreparedStatement pstmt = conn.prepareStatement(sql);
     ResultSet rs = pstmt.executeQuery()) {
    // 使用資源
} // 自動按 rs → pstmt → conn 順序關閉

// 手動模式 (舊版維護)
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
    conn = ds.getConnection();
    pstmt = conn.prepareStatement(sql);
    rs = pstmt.executeQuery();
    // ...
} finally {
    closeQuietly(rs);
    closeQuietly(pstmt);
    closeQuietly(conn); // 歸還連線池
}

現代專案建議使用 HikariCP 連線池 + MyBatis/JOOQ/Spring JDBC 封裝層,極少直接操作 Statement。但理解底層機制對排查連線洩漏、死鎖、執行計畫問題至關重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved