// 失效寫法
String sql = "SELECT * FROM goods ORDER BY ? LIMIT ?, ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, "price"); // 排序字段
ps.setInt(2, 0); // offset
ps.setInt(3, 20); // pageSize
ResultSet rs = ps.executeQuery();
// 結果:始終按默認順序(通常是主鍵)排序,忽略 ORDER BY 參數
JDBC PreparedStatement 參數綁定(? 佔位符)僅適用於數據值(DML 值),不適用於 SQL 結構元素:
WHERE price > ?INSERT INTO t (c) VALUES (?)SELECT * FROM ?SELECT ? FROM tORDER BY ?ORDER BY price ?驅動將 ORDER BY ? 處理為 ORDER BY 'price'(字符串字面量),而非 ORDER BY price(標識符),導致排序無效。
public List queryGoods(String orderBy, int offset, int limit) {
// 1. 白名單驗證(核心防注入)
Set allowedColumns = Set.of("price", "create_time", "sales", "name");
String safeOrderBy = allowedColumns.contains(orderBy) ? orderBy : "id";
// 2. 方向驗證
String direction = "ASC".equalsIgnoreCase(direction) ? "ASC" : "DESC";
// 3. 拼接 SQL(結構部分)
String sql = "SELECT * FROM goods ORDER BY " + safeOrderBy + " " + direction + " LIMIT ?, ?";
// 4. 參數綁定(數據部分)
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, offset);
ps.setInt(2, limit);
return executeQuery(ps);
}
}
public List queryDynamic(Map params) {
StringBuilder sql = new StringBuilder("SELECT * FROM goods WHERE 1=1");
List
List selectGoods(@Param("category") String category,
@Param("orderBy") String orderBy,
@Param("direction") String direction,
@Param("offset") int offset,
@Param("limit") int limit);
注意:MyBatis 中 ${} 直接拼接,**必須在 Service 層做白名單驗證**,否則極高風險 SQL 注入。
-- 偏移量分頁深分頁性能差:LIMIT 1000000, 20 掃描 100 萬行
-- 游標分頁:記住上一頁最後一條記錄的排序值
SELECT * FROM goods
WHERE price > ? -- 上一頁最後價格
ORDER BY price ASC
LIMIT 20;
-- 複合排序游標
WHERE (price > ?) OR (price = ? AND id > ?)
ORDER BY price, id
LIMIT 20;
| 數據庫 | LIMIT 參數綁定 | ORDER BY 參數綁定 | 推薦寫法 |
|---|---|---|---|
| MySQL 5.0.7+ | ✅ 支持 | ❌ 不支持 | 拼接 ORDER BY,綁定 LIMIT |
| PostgreSQL | ✅ 支持 | ❌ 不支持 | 拼接 ORDER BY,綁定 LIMIT/OFFSET |
| SQL Server | ❌ 不支持 (OFFSET/FETCH 需常量) | ❌ 不支持 | 全拼接或動態 SQL |
| Oracle | ❌ 不支持 (ROWNUM 需嵌套) | ❌ 不支持 | 全拼接 |
PreparedStatement 綁定所有數據值JOOQ 等類型安全 SQL 構建器