PreparedStatement 是 JDBC 防禦 SQL 注入的核心利器,通過預編譯 + 參數綁定徹底隔離代碼與數據。本文從注入漏洞演示、PreparedStatement 核心機制、參數設置方法、實戰代碼模板、常見誤區五個維度,幫你徹底掌握安全數據庫操作。
場景:用戶登錄,拼接 SQL 字符串。
// 危險代碼 String sql = "SELECT * FROM `master` WHERE `name`='" + name + "' AND `password`='" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);
攻擊輸入:
liubei123' OR '1'='1實際執行 SQL:
SELECT * FROM `master` WHERE `name`='liubei' AND `password`='123' OR '1'='1'
'1'='1' 恆真,繞過密碼驗證,登錄成功!
? 標記參數位置,非 SQL 語法一部分。setXxx(index, value) 將值安全傳遞給數據庫,數據庫按值處理,不再解析為 SQL 片段。setInt、setString、setTimestamp 等強類型方法,自動處理轉義、格式化。String sql = "UPDATE pet SET health=?, love=? WHERE id=?"; // 參數用 ?
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DriverManager.getConnection(url, user, pass);
pstmt = conn.prepareStatement(sql); // 預編譯
pstmt.setInt(1, 1234); // 第 1 個 ? → health
pstmt.setInt(2, 88); // 第 2 個 ? → love
pstmt.setInt(3, 2); // 第 3 個 ? → id
int rows = pstmt.executeUpdate(); // DML 返回影響行數
System.out.println("更新行數: " + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 逆序關閉
if (pstmt != null) try { pstmt.close(); } catch (SQLException ignore) {}
if (conn != null) try { conn.close(); } catch (SQLException ignore) {}
}
| Java 類型 | set 方法 | 對應 SQL 類型示例 |
|---|---|---|
| String | setString(index, val) | VARCHAR, CHAR, TEXT |
| int / Integer | setInt(index, val) | INT, SMALLINT |
| long / Long | setLong(index, val) | BIGINT |
| double / Double | setDouble(index, val) | DOUBLE, DECIMAL |
| boolean / Boolean | setBoolean(index, val) | BIT, BOOLEAN |
| Date / LocalDate | setDate(index, val) | DATE |
| Timestamp / LocalDateTime | setTimestamp(index, val) | DATETIME, TIMESTAMP |
| byte[] | setBytes(index, val) | BLOB, VARBINARY |
| InputStream | setBinaryStream / setBlob | BLOB (大文件流式寫入) |
| Reader | setCharacterStream / setClob | CLOB (大文本流式寫入) |
| 誤區 | 後果 | 正確做法 |
|---|---|---|
用 prepareStatement 但仍拼接字符串 | 失去防注入保護 | 所有動態值均用 ? 佔位符 |
LIKE 查詢拼接 %:name LIKE ? → setString(1, "%" + kw + "%") | 正確!通配符屬於參數值一部分 | 在 Java 代碼拼接 %,而非 SQL 中 |
| IN 子句動態參數數量不固定 | 無法直接綁定集合 | 動態生成 ?,?,? 佔位符字符串,循環 set |
忽略 executeQuery vs executeUpdate 區別 | 執行 DML 用 query 返回 ResultSet 為空 | SELECT 用 query,INSERT/UPDATE/DELETE 用 update |
| 未關閉資源導致連接洩漏 | 連接池耗盡,應用掛起 | try-with-resources (JDBC 4.1+) 或 finally 逆序關閉 |