必须用${},因ORDER BY字段是SQL语法结构而非数据值,#{}会加引号导致排序失效或报错;但${}存在SQL注入风险,须通过白名单校验字段名和排序方向,禁止用户直控。

MyBatis 中的 ${} 不是参数占位,而是纯文本替换 —— 它在 SQL 解析阶段就将变量值原样插入语句中,不经过 JDBC 预编译,也不做任何转义或类型处理。这决定了它既灵活又危险,尤其在 ORDER BY 场景下必须谨慎对待。
为什么 ORDER BY 必须用 ${} 而不能用 #{}
因为数据库排序字段(如 user_name、create_time)是 SQL 语法结构的一部分,不是数据值。JDBC 的 PreparedStatement 只允许对“值”使用 ? 占位符,不允许对列名、表名、关键字等结构元素占位。
- 用
#{orderField}会生成类似ORDER BY 'user_name'的 SQL —— 单引号把字段名当成了字符串字面量,排序失效,甚至报错 - 用
${orderField}则直接拼成ORDER BY user_name,符合 SQL 语法规则,能正确生效
直接拼接带来的核心风险:SQL 注入
${} 等价于 Java 的 String.format() 或字符串拼接,若用户可控输入未经约束就进入 ${},攻击者可注入恶意 SQL 片段。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 例如传入
orderField = "id ASC, (SELECT password FROM users LIMIT 1)",可能触发子查询泄露敏感数据 - 更危险的是传入
orderField = "id; DROP TABLE user--"(取决于数据库是否支持多语句)
安全使用 ${} 动态排序的实操要点
关键不是“能不能用”,而是“怎么控制输入来源和内容”。必须切断用户直连 SQL 结构的路径。
立即学习“Java免费学习笔记(深入)”;
- 禁止前端直接传任意字段名;只允许从预设白名单中选择,如
Map.of("name", "user_name", "time", "create_time", "score", "total_score") - 后端接收参数后,先校验是否属于白名单键,再映射为真实列名,再传入
${}—— 用户永远不接触真实列名字符串 - 排序方向(
ASC/DESC)同样需白名单限制,禁止传入"ASC; TRUNCATE log_table--"类构造 - 避免在同一个
${}中拼接多个动态部分(如${field} ${direction}),应分别校验,防止方向参数被用于闭合引号或注入注释
替代方案:尽量减少 ${} 使用场景
并非所有排序都需要动态字段。可考虑更安全的折中方式:
- 固定几个常用排序组合,每个对应独立的
<if>分支,全部用#{}处理值(如WHERE条件),仅结构静态 - 业务层根据参数选择不同 SQL 片段,而非依赖字符串拼接
- 数据量不大时,交由前端排序;大数据分页场景才启用后端动态排序,并严格走白名单流程

















