必须用 #{} 而非 ${} 实现安全模糊查询:mysql 用 concat('%', #{username}, '%'),多库项目用 标签组装 pattern,mybatis-plus 直接调用 like() 方法,同时需在 controller 层校验输入长度与非空。

用 #{} 而不是 ${} 是底线
只要在 WHERE 条件里拼 LIKE,就绝不能用 ${username}。它会把用户输入原样塞进 SQL,比如传入 admin' OR '1'='1,最终生成的语句就是:WHERE username LIKE '%admin' OR '1'='1%' —— 这直接绕过条件,查出全表数据。
而 #{username} 交由 JDBC 的 PreparedStatement 处理,输入值会被自动加引号并转义,哪怕内容含单引号、分号或注释符,也只当字符串字面量,不会参与语法解析。
- 错误写法(高危):
WHERE username LIKE '%${username}%' - 正确前提:
#{username}必须配合数据库函数或预处理完成通配符拼接,不能直接套在单引号里
CONCAT() 是 MySQL 最稳妥的拼接方式
MySQL 不支持 '%' || #{name} || '%' 这种写法,也不该用字符串拼接(如 '%' + #{name} + '%')—— 那是 SQL Server 的语法,硬套会报错。
必须用 CONCAT() 函数让数据库层面完成拼接,确保 #{name} 始终作为独立参数传入:
<select id="findByUsernameLike" resulttype="User">
SELECT id, username, email FROM t_user
WHERE username LIKE CONCAT('%', #{username}, '%')
</select>
- 传入
张→ 实际执行:LIKE CONCAT('%', '张', '%')→ 等价于LIKE '%张%' - 传入
admin' OR '1'='1→ 实际执行:LIKE CONCAT('%', 'admin'' OR ''1''=''1', '%'),单引号被 JDBC 自动转义,无害 - 注意:
CONCAT()在 MySQL 5.0+ 全版本支持;若用低版本,需升级或改用CONCAT_WS('', '%', #{name}, '%')
用 <bind></bind> 标签提前组装 pattern 更灵活
当需要同时支持前缀、后缀、全匹配等不同模糊策略时,<bind></bind> 可以在 XML 内部完成字符串拼接,避免 Java 层污染逻辑:
<select id="searchUsers" parametertype="map" resulttype="User"><bind name="pattern" value="'%' + username + '%'"></bind>
SELECT id, username, email FROM t_user
WHERE username LIKE #{pattern}
</select>
-
value表达式中用的是 Java 字符串拼接语法(+),不是 SQL;MyBatis 在解析时就把%和参数值合成一个字符串,再作为#{pattern}绑定 - 兼容所有数据库,不依赖
CONCAT或||,适合多数据库项目 - 注意:不要写成
value="'%' + ${username} + '%'",${}仍会触发注入
MyBatis-Plus 用户直接用 like() 就行
如果你用的是 MyBatis-Plus,根本不用手写 SQL —— QueryWrapper 的 like() 方法内部已封装安全逻辑:
QueryWrapper<user> wrapper = new QueryWrapper();
wrapper.like("username", userInput);
List<user> users = userMapper.selectList(wrapper);</user></user>
- 它底层自动调用
CONCAT(MySQL)或等效函数(Oracle/SQL Server),且全程使用#{}绑定 - 无需关心数据库方言,也不用在 XML 里写动态 SQL
- 但注意:别误用
like("username", "%" + userInput + "%"),这等于把拼接逻辑丢回 Java 层,万一没过滤就危险
真正容易被忽略的点是:哪怕用了 CONCAT 或 <bind></bind>,如果前端传入空字符串、null 或超长字符串(比如 10MB 的 base64),仍可能引发性能抖动或 OOM。建议在 Controller 层做长度截断和非空校验,而不是只依赖 SQL 层防护。











