wrapper本身不防注入,安全取决于使用方式;内置eq、like等方法通过preparedstatement参数绑定实现防注入,lambdaquerywrapper通过编译期字段引用提升可靠性,apply和@select需严防结构注入。

Wrapper 本身不防注入,安全完全取决于你怎么用——只要不用字符串拼接、不把用户输入当 SQL 结构用,内置方法(eq、like、in等)默认就是安全的。
为什么 eq 和 like 等方法默认不会导致 SQL 注入
它们底层全部走 JDBC PreparedStatement 参数绑定,和 MyBatis 的 #{} 本质一致:参数值只作为数据传入,不参与 SQL 解析。
-
queryWrapper.eq("username", "admin' OR '1'='1")生成的是WHERE username = ?,数据库只会去查用户名等于这个完整字符串的记录 -
queryWrapper.like("name", keyword)是安全的;但写成"%" + keyword + "%"就可能出问题——尤其当keyword含%或_且没配escape规则时,语义会被破坏 - 动态条件记得加布尔开关:
queryWrapper.eq(name != null, "name", name),避免空值误加条件
为什么 LambdaQueryWrapper 比 QueryWrapper 更可靠
关键不在 Wrapper 类型,而在字段引用方式:User::getUsername 在编译期就锁定了字段路径,运行时通过反射提取字段名,不经过字符串解析。
-
queryWrapper.eq("status", input)中的"status"是纯字符串,若来自前端或配置项,可能被篡改为"status; DROP TABLE user" -
lambdaWrapper.eq(User::getStatus, input)的字段来源是类结构,外部无法污染 - 排序字段同理:
lambdaWrapper.orderByAsc(User::getCreateTime)彻底避开字符串字段名风险
apply 是唯一能主动打开注入通道的地方
apply 不管你前面用了多少个 User::getXXX,只看你传进去的 SQL 片段干不干净。它不区分“结构”和“值”,必须手动控制。
- ❌ 危险:
wrapper.apply("status = '" + statusValue + "'")——statusValue是"1'; TRUNCATE user; --"就直接执行 - ✅ 安全:
wrapper.apply("status = {0}", statusValue)——{0}触发参数绑定,等价于? - ⚠️ 注意:
{0}只对值生效,不能用于字段名、表名、排序方向(如ORDER BY {0}会报错或被忽略) - 真要动态结构?必须白名单校验后映射到方法引用,例如:
if ("price".equals(sortField)) wrapper.orderByAsc(User::getPrice)
使用 @Select + ${ew.customSqlSegment} 时的隐藏风险
这种写法常见于需要自定义 SQL 主体但保留 Wrapper 动态条件的场景,但容易踩三个坑:
- SQL 注入风险:如果
customSqlSegment被恶意构造(比如前端传参控制了整个WHERE),而你又没做校验,就等于放行了结构注入 - 条件重复:自定义 SQL 已含
WHERE status = 1,又在QueryWrapper里加.eq("status", 2),最终变成WHERE status = 1 AND status = 2,结果永远为空 - 分页失效:某些分页插件无法识别
${ew.customSqlSegment}中的嵌套结构,导致 count 查询错位
最易被忽略的一点:字段名、排序字段、表别名这些“SQL 结构部分”,哪怕用了 LambdaQueryWrapper,只要落到 apply 或 @Select 的字符串拼接里,就立刻失去所有保护。安全不是选对 Wrapper,而是守住“结构不可变、值必绑定”这条线。











