場景需求
需要根據不同的列進行對應的排序操作,實現動態列名排序 類似🐟動態查詢或更新
但是JDBC預編譯模式下占位符的排序字段失效
SQL語句
分頁查詢
select * from (select t.*, rownum rn from(select * from emp order by empno desc) t where rownum <= 5) where rn > 0;
指定列排序失效
select * from (select t.*, rownum rn from(select * from emp order by ? desc) t where rownum <= 5) where rn > 0;
臨時解決字符串拼接
- SQL注入問題
select * from (select t.*, rownum rn from(select * from emp order by "+ empno + " desc) t where rownum <= 5) where rn > 0;
預編譯注入排序列名排序失效
PreparedStatement
執行SQL時,如果order by之后的排序字段使用占位符,通過setString設置值的話,會導致排序失效
// 4 SQL 語句被預編譯并存儲在 PreparedStatement 對象中。然后可以使用此對象多次高效地執行該語句。
PreparedStatement pstmt = conn.prepareStatement(sql);// 如果SQL有?號 參數需要注入
pstmt.setString(1, sortColumnName); // 1 代表 第一個? 從1開始 以此類推
源碼
/*** An object that represents a precompiled SQL statement.* <P>A SQL statement is precompiled and stored in a* {@code PreparedStatement} object. This object can then be used to* efficiently execute this statement multiple times.** <P><B>Note:</B> The setter methods ({@code setShort}, {@code setString},* and so on) for setting IN parameter values* must specify types that are compatible with the defined SQL type of* the input parameter. For instance, if the IN parameter has SQL type* {@code INTEGER}, then the method {@code setInt} should be used.** <p>If arbitrary parameter type conversions are required, the method* {@code setObject} should be used with a target SQL type.* <P>* In the following example of setting a parameter, {@code con} represents* an active connection:* <pre>{@code* BigDecimal sal = new BigDecimal("153833.00");* PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES* SET SALARY = ? WHERE ID = ?");* pstmt.setBigDecimal(1, sal);* pstmt.setInt(2, 110592);* }</pre>** @see Connection#prepareStatement* @see ResultSet* @since 1.1*/
public interface PreparedStatement extends Statement {/*** Sets the designated parameter to the given Java {@code String} value.* The driver converts this* to an SQL {@code VARCHAR} or {@code LONGVARCHAR} value* (depending on the argument's* size relative to the driver's limits on {@code VARCHAR} values)* when it sends it to the database.** @param parameterIndex the first parameter is 1, the second is 2, ...* @param x the parameter value* @throws SQLException if parameterIndex does not correspond to a parameter* marker in the SQL statement; if a database access error occurs or* this method is called on a closed {@code PreparedStatement}*/void setString(int parameterIndex, String x) throws SQLException;...
}
核心解釋
- 將指定的參數設置為給定的Java{@code String}值。
- 驅動程序轉換此轉換為SQL{@code VARCHAR}或{@code LONGVARCHAR}值
原因
PreparedStatement用占位符防止SQL注入的原理是,在為占位符設置值時,會將值轉為字符串,然后轉義,再將值放入反引號中,放置在占位符的位置上。
因此,當排序字段使用占位符后,原來的排序語句 order by empno
(假設排序字段是empno
),在實際執行時變成了 order by empno
,根據字段排序變成了根據字符串常量值empno
排序,導致排序失效,甚至任意的注入數值都不影響前面的查詢結果
情況一
使用預編譯的數據庫操作對象在order by后面設置占位符,再通過pstmt.setString()
方法填入參數會導致排序失敗
情況二
使用mybatis的時候,在mapper sql映射.xml文件中,在order by 后面使用 #{參數名}
依然會導致排序失敗,因為mybatis #{}
使用的是PrepareStatement
解決辦法
#{}
方式傳參數只能處理值參數 不能傳遞表名,字段等參數${}
字符串替換,可以動態處理表名,字段參數
把#{}
改成${}
, #{}
是預編譯,相當于PrepareStatement
;${}
是普通字符串的拼接,相當于Statement
但是必須注意SQL注入的風險,對參數做好校驗處理