mybatis-plus wrapper 本身不防注入,安全取决于使用方式;eq/like 等内置方法通过 preparedstatement 参数绑定天然安全;apply 方法需用 {0} 绑定参数,字段名、排序等结构部分必须白名单校验。

MyBatis-Plus 的 Wrapper 对象本身不自动防注入——它只是个条件容器,安全与否完全取决于你往里塞什么。只要不用字符串拼接、不滥用 ${} 或手动拼 SQL 片段,QueryWrapper 和 LambdaQueryWrapper 就是天然安全的构建方式。
Wrapper 的 eq/like 等方法为什么默认安全
这些方法底层走的是 JDBC PreparedStatement 参数绑定,和 MyBatis 的 #{} 本质一致:参数值不会参与 SQL 解析,只作为数据传入。
-
queryWrapper.eq("name", userInput)→ 生成WHERE name = ?,userInput被 JDBC 驱动转义后设为参数值 - 即使
userInput是"admin' OR '1'='1",也不会改变 SQL 结构,只会查一个叫这个怪名字的用户 - 所有内置方法(
gt、between、in、like)都遵循该机制,无需额外处理
apply 方法是 Wrapper 里最危险的入口
apply 允许插入任意 SQL 片段,一旦用错就等于主动打开注入通道。
- ❌ 危险写法:
queryWrapper.apply("status = '" + statusParam + "'")—— 直接拼接,statusParam为"1'; DROP TABLE user; --"就完蛋 - ✅ 安全写法:
queryWrapper.apply("status = {0}", statusParam)——{0}触发参数绑定,等价于?占位符 - ⚠️ 注意:
{0}只对参数值生效,不能用于字段名、表名、排序方向等结构部分(如ORDER BY {0}会报错或被忽略)
字段名/表名/排序字段动态化时必须白名单校验
Wrapper 不处理结构动态化,比如 orderByAsc(sortField) 中的 sortField 是直接拼进 SQL 的,不受参数绑定保护。
- 前端传来的
sort=price; DROP TABLE user会被原样塞进 SQL,变成ORDER BY price; DROP TABLE user - 必须显式校验:
if (!List.of("price", "create_time", "sales").contains(sortField)) { throw new IllegalArgumentException(); } - 更稳妥的做法是用
LambdaQueryWrapper+ 方法引用:lambdaWrapper.orderByAsc(User::getPrice),彻底避开字符串字段名
like 模糊查询的通配符陷阱
like 方法本身安全,但如果你自己拼 %,就可能引入风险——尤其当用户输入含 %、_ 或转义字符时。
- ❌ 错误:
queryWrapper.like("name", "%" + keyword + "%")—— 若keyword含%且未配置 escape,可能绕过预期逻辑 - ✅ 推荐:
queryWrapper.like("name", keyword),再配合like自带的escape参数或数据库级配置(如 MySQL 的ESCAPE '\') - 更可控的方式:用
apply绑定 + 显式 escape:queryWrapper.apply("name LIKE CONCAT('%', {0}, '%') ESCAPE '\'", keyword)
真正容易被忽略的点是:apply 的 {0} 只保参数值,不保语法结构;字段名、排序方向、分组字段、函数名这些“代码部分”永远需要独立校验或硬编码。安全不是 Wrapper 给的,是你控制输入边界的程度决定的。











