Wrapper 的 eq/like 等方法默认安全,因其底层使用 JDBC PreparedStatement 参数绑定,参数值不参与 SQL 解析;LambdaQueryWrapper 更可靠在于编译期锁定字段路径,避免字符串注入;apply 和 inSql 等需严格使用参数占位符或白名单校验。

Wrapper 的 eq/like 等方法为什么默认安全
因为它们底层走的是 JDBC PreparedStatement 参数绑定,和 MyBatis 的 #{} 本质一致:参数值不参与 SQL 解析,只作为数据传入。
比如 queryWrapper.eq("username", userInput) 生成的 SQL 是 WHERE username = ?,即使 userInput 是 "admin' OR '1'='1",也只会查一个用户名叫这个字符串的记录,不会破坏结构。
- 所有内置方法(
gt、between、in、like)都遵循该机制,无需额外处理 -
like方法本身安全,但手动拼"%" + keyword + "%"就可能出问题——尤其当keyword含%或_且未配escape规则时 - 推荐写法:
queryWrapper.like("name", keyword),让框架统一处理通配符和转义
为什么 LambdaQueryWrapper 比 QueryWrapper 更可靠
关键不在 Wrapper 类型,而在字段引用方式:用 User::getUsername 替代 "username" 字符串。
QueryWrapper 中的字段名是纯字符串,一旦来自不可信源(比如前端传参、配置项),就可能被篡改;而 LambdaQueryWrapper 的 User::getStatus 在编译期就锁定字段路径,运行时通过反射提取字段名,不经过字符串解析,天然绕过字段名拼错或注入风险。
- 错误示范:
wrapper.orderByAsc(sortParam),若sortParam是"update_time; DROP TABLE user",会直接拼进 SQL - 安全做法:白名单校验后转成方法引用,如
if ("price".equals(sortField)) wrapper.orderByAsc(User::getPrice) -
LambdaQueryWrapper不能防住apply—— 它只管字段引用,不管你怎么塞 SQL 片段
apply 方法怎么用才不踩坑
apply 是 Wrapper 里唯一能主动打开注入通道的地方。它不区分你前面用了多少个 User::getXXX,只看你传进去的 SQL 片段干不干净。
- ❌ 危险:
wrapper.apply("status = '" + statusValue + "'")—— 直接拼接,statusValue是"1'; TRUNCATE user; --"就完蛋 - ✅ 安全:
wrapper.apply("status = {0}", statusValue)——{0}触发参数绑定,等价于? - ⚠️ 注意:
{0}只对值生效,不能用于字段名、表名、排序方向等结构部分(如ORDER BY {0}会报错或被忽略) - 真要动态结构?必须白名单校验后再映射到方法引用,或用
SqlInjectionUtils.check()做兜底
UpdateWrapper 和 inSql 的特殊风险点
UpdateWrapper 和 QueryWrapper 在参数绑定逻辑上一致,但 inSql 这类方法容易被误用为“万能拼接口”。
- ❌ 错误:
wrapper.inSql("id", "SELECT id FROM t_user WHERE name LIKE '%" + keyword + "%'")—— 子查询里的keyword被直接拼接 - ✅ 正确:
wrapper.inSql("id", "SELECT id FROM t_user WHERE name LIKE #{keyword}"),或改用selectObjs分步查出 ID 再传入in -
UpdateWrapper的set方法虽安全,但若配合apply或setSql,同样可能引入结构风险 - 别依赖
SqlUtils.sqlInject()全局过滤——它只是简单替换危险字符,无法覆盖所有边界场景,不如从源头杜绝拼接

















