setXxx() 方法綁定至佔位符 ?,資料庫引擎將參數視為純資料,**永不參與 SQL 邏輯解析**。' OR '1'='1,也會被視為字串字面值查詢,而非改變 WHERE 條件邏輯。// 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();
user(id, name, pwd, salary, email)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 (視網路/資料庫負載波動)。
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 倍** 吞吐量。
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 輸入 username: admin' -- 即可繞過密碼驗證
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 只能用於 DML | SELECT 同樣受益於預編譯計畫緩存 |
| 在循環中重複創建 PreparedStatement | 循環外創建一次,循環內僅 setXxx + execute |
| 使用 LIKE 時將 % 寫在 SQL 中 | ps.setString(1, "%" + keyword + "%"); 參數含通配符 |
| 未關閉 PreparedStatement 導致游標洩漏 | try-with-resources 或 finally 區塊確實 close() |
| 以為所有資料庫都完美支援預編譯 | 部分老版本/嵌入式 DB 模擬預編譯,仍建議統一使用 |
PreparedStatement = 效能 + 安全 + 可維護性。除極少數 DDL 或動態 SQL 結構變化場景外,**所有資料庫操作首選 PreparedStatement** 是現代 Java 開發的鐵律。