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

JDBC PreparedStatement 預編譯深度解析:從基礎到通用封裝

     編輯:Java 相關知識

為什麼選擇 PreparedStatement?

對比 Statement,PreparedStatement 提供三大核心優勢:

  1. 預編譯優化:SQL 語句發送給數據庫後立即編譯並緩存執行計劃,同一 SQL 重複執行時省去解析、編譯、優化開銷,批量操作效能顯著提升。
  2. 防止 SQL 注入:參數佔位符(?)與數據分離,驅動程序負責轉義,從根本上杜絕拼接 SQL 帶來的注入風險。
  3. 代碼可讀性與維護性:參數化設置(setXxx(index, value))清晰分離 SQL 模板與業務數據,避免字串拼接錯誤。

適用場景:同一 SQL 結構、不同參數值的重複執行(批量插入、條件查詢、分頁等)。單次執行的臨時 SQL 用 Statement 即可,無額外收益。

核心 API 快速入門

// 1. 獲取預編譯語句對象
String sql = "SELECT * FROM student WHERE YEAR(birthday) BETWEEN ? AND ?";
PreparedStatement ps = conn.prepareStatement(sql);

// 2. 設置參數(索引從 1 開始)
ps.setString(1, "1987");
ps.setString(2, "1990");

// 3. 執行查詢
ResultSet rs = ps.executeQuery();

// 4. 執行增刪改
int affected = ps.executeUpdate();

通用封裝:從固定參數到動態參數數組

版本 1:固定兩參數查詢

public ResultSet queryByRange(String sql, String param1, String param2) throws SQLException {
    getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setString(1, param1);
    ps.setString(2, param2);
    return ps.executeQuery();
}

// 調用
ResultSet rs = dao.queryByRange(
    "SELECT * FROM t_user WHERE YEAR(birthdate) BETWEEN ? AND ?",
    "1992", "1992"
);

版本 2:動態參數數組查詢(推薦)

/**
 * 通用預編譯查詢
 * @param sql 含佔位符的 SQL
 * @param params 參數值數組,順序對應佔位符
 * @return ResultSet(調用者負責關閉)
 */
public ResultSet query(String sql, Object... params) throws SQLException {
    getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    for (int i = 0; i < params.length; i++) {
        ps.setObject(i + 1, params[i]); // setObject 自動處理類型轉換
    }
    return ps.executeQuery();
}

// 調用示例
// 生日範圍查詢
ResultSet rs1 = dao.query(
    "SELECT * FROM t_user WHERE YEAR(birthdate) BETWEEN ? AND ?",
    "1992", "1992"
);
// ID 範圍查詢
ResultSet rs2 = dao.query(
    "SELECT * FROM t_user WHERE id BETWEEN ? AND ?",
    100, 200
);

版本 3:通用增刪改(支持批量)

/**
 * 通用預編譯增刪改
 * @param sql 含佔位符的 SQL
 * @param params 參數值數組
 * @return 影響行數
 */
public int executeUpdate(String sql, Object... params) throws SQLException {
    int count = 0;
    getConnection();
    try {
        PreparedStatement ps = conn.prepareStatement(sql);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
        }
        count = ps.executeUpdate();
    } finally {
        close(); // 關閉連接、語句、結果集
    }
    return count;
}

// 批量插入 10 條測試數據
for (int i = 0; i < 10; i++) {
    dao.executeUpdate(
        "INSERT INTO jdbc_test(username, password) VALUES(?, ?)",
        "user" + i, "pwd" + i
    );
}

// 批量刪除
dao.executeUpdate(
    "DELETE FROM t_user WHERE id BETWEEN ? AND ?",
    1010, 1030
);

進階最佳實踐

1. 批量處理優化

大量寫入時,使用 addBatch() + executeBatch() 減少網絡往返:

public int[] batchInsert(String sql, List paramList) throws SQLException {
    getConnection();
    conn.setAutoCommit(false); // 手動提交事務
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
        for (Object[] params : paramList) {
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
            ps.addBatch();
        }
        int[] results = ps.executeBatch();
        conn.commit();
        return results;
    } catch (SQLException e) {
        conn.rollback();
        throw e;
    } finally {
        conn.setAutoCommit(true);
        close();
    }
}

2. 參數設置類型安全

雖然 setObject 通用,但明確類型方法(setInt、setString、setTimestamp)能更早發現類型不匹配,建議在核心業務 DAO 中使用強類型設置。

3. 資源管理模板

// 使用 try-with-resources (JDK 7+) 自動關閉資源
public ResultSet queryAutoClose(String sql, Object... params) throws SQLException {
    try (Connection conn = dataSource.getConnection();
         PreparedStatement ps = conn.prepareStatement(sql)) {
        for (int i = 0; i < params.length; i++) {
            ps.setObject(i + 1, params[i]);
        }
        // 注意:ResultSet 需在 Connection 關閉前讀完,或獨立管理
        return ps.executeQuery();
    }
}

4. 防止 SQL 注入的本質

預編譯階段數據庫已生成執行計劃,參數作為純數據傳輸,不會再經過 SQL 解析器,因此即使參數包含 ' OR '1'='1 也僅作為字面值查詢,無法改變 SQL 語義。

常見錯誤與避坑指南

錯誤現象原因修正
佔位符索引報錯索引從 1 開始,而非 0循環用 i + 1
參數數量不匹配SQL 中 ? 數量與 params 長度不一致開發期單元測試驗證
日期類型插入失敗直接 setObject 可能類型映射失敗用 setTimestamp 或 setDate
批量執行部分失敗無感知executeBatch 返回數組需逐項檢查檢查返回值,或捕獲 BatchUpdateException
ResultSet 使用中連接被關閉DAO 方法關閉連接,調用者還在遍歷調用者負責關閉,或在 Service 層統一事務邊界

總結

PreparedStatement 是 JDBC 開發的基石,掌握「預編譯機制、參數化設置、批量優化、資源管理」四大核心,並封裝為通用 DAO 基類,能顯著提升開發效率與代碼質量。實戰中建議:查詢用動態參數數組封裝,寫入用批量事務,資源用 try-with-resources,類型用強類型設置,構建安全、高效、可維護的持久層。

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