首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

JDBC PreparedStatement 完全指南:預編譯提升效能、防禦 SQL 注入、批次處理最佳實踐

     編輯:MySQL 綜合教學

為什麼選擇 PreparedStatement?

1. 效能提升:預編譯機制

  • Statement:適用於靜態 SQL,每次執行都要經歷「解析 → 編譯 → 優化 → 執行」完整流程。
  • PreparedStatement:SQL 發送給資料庫時即預先編譯並緩存執行計畫。後續執行僅需傳遞參數值,直接複用執行計畫,大幅降低 CPU 開銷與網路交互延遲。
  • 適用場景:相同結構、僅參數不同的重複執行 SQL (如批次插入、條件查詢)。

2. 安全防禦:原生免疫 SQL 注入

  • 參數值通過 setXxx() 方法綁定至佔位符 ?,資料庫引擎將參數視為純資料,**永不參與 SQL 邏輯解析**。
  • 即使輸入 ' OR '1'='1,也會被視為字串字面值查詢,而非改變 WHERE 條件邏輯。
  • Statement 拼接字串極易注入,PreparedStatement 從架構層面杜絕此風險。

核心 API 與使用流程

// 1. 取得連線
Connection conn = DriverManager.getConnection(url, user, pass);

// 2. 預編譯 SQL (佔位符 ? 對應 IN 參數)
String sql = "UPDATE emp SET job = ? WHERE empno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);

// 3. 綁定參數 (索引從 1 開始,對應 ? 順序)
pstmt.setString(1, "MANAGER");  // 第 1 個 ?
pstmt.setInt(2, 1001);          // 第 2 個 ?

// 4. 執行
int rows = pstmt.executeUpdate();  // DML: INSERT/UPDATE/DELETE
// ResultSet rs = pstmt.executeQuery(); // DQL: SELECT
// boolean hasResult = pstmt.execute();  // 通用執行

// 5. 釋放資源 (建議 try-with-resources Java 7+)
pstmt.close();
conn.close();

實戰對比:插入 1000 筆資料效能測試

環境

  • 資料表:user(id, name, pwd, salary, email)
  • 測試資料:ID 7000~7999 (Statement) vs 9000~9999 (PreparedStatement)

Statement 版本 (每次拼接 SQL)

Statement stmt = conn.createStatement();
long start = System.currentTimeMillis();
for (int i = 7000; i < 8000; i++) {
    String sql = "INSERT INTO user VALUES(" + i + ",'test" + i + "','12345',5000,'test" + i + "@qq.com')";
    stmt.executeUpdate(sql);
}
System.out.println("耗時: " + (System.currentTimeMillis() - start) + " ms");

測試結果:約 1,200~1,500 ms (視網路/資料庫負載波動)。

PreparedStatement 版本 (預編譯 + 參數綁定)

String sql = "INSERT INTO user VALUES(?, ?, '123456', ?, ?)";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 9000; i < 10000; i++) {
    ps.setInt(1, i);
    ps.setString(2, "test" + i);
    ps.setInt(3, 5000);
    ps.setString(4, "test" + i + "@qq.com");
    ps.executeUpdate();
}
System.out.println("耗時: " + (System.currentTimeMillis() - start) + " ms");

測試結果:約 300~500 ms,**效能提升 3~4 倍**。

進階技巧:批次處理

搭配 addBatch() / executeBatch() 可進一步減少網路往返:

conn.setAutoCommit(false);  // 手動提交事務
PreparedStatement ps = conn.prepareStatement("INSERT INTO user VALUES(?, ?, ?, ?, ?)");
for (int i = 0; i < 10000; i++) {
    ps.setInt(1, i);
    ps.setString(2, "name" + i);
    ps.setString(3, "pwd");
    ps.setInt(4, 5000);
    ps.setString(5, "email" + i);
    ps.addBatch();
    if (i % 1000 == 0) ps.executeBatch(); // 分批提交避免記憶體溢位
}
ps.executeBatch(); // 提交剩餘
conn.commit();

批次處理可再提升 **5~10 倍** 吞吐量。

防禦 SQL 注入實例:登入驗證

不安全寫法 (Statement 拼接)

String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 輸入 username: admin' --  即可繞過密碼驗證

安全寫法 (PreparedStatement)

String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
ResultSet rs = ps.executeQuery();
// 任何惡意輸入均視為字面值,邏輯結構不可變

常見誤區與最佳實踐

誤區正確做法
以為 PreparedStatement 只能用於 DMLSELECT 同樣受益於預編譯計畫緩存
在循環中重複創建 PreparedStatement循環外創建一次,循環內僅 setXxx + execute
使用 LIKE 時將 % 寫在 SQL 中ps.setString(1, "%" + keyword + "%"); 參數含通配符
未關閉 PreparedStatement 導致游標洩漏try-with-resources 或 finally 區塊確實 close()
以為所有資料庫都完美支援預編譯部分老版本/嵌入式 DB 模擬預編譯,仍建議統一使用

總結

PreparedStatement = 效能 + 安全 + 可維護性。除極少數 DDL 或動態 SQL 結構變化場景外,**所有資料庫操作首選 PreparedStatement** 是現代 Java 開發的鐵律。

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