mybatis-plus的querywrapper本身不引入sql注入风险,但apply、insql、last等方法若字符串拼接或未校验动态字段名则极易导致注入;eq/like等内置方法因使用preparedstatement预编译而默认安全。

MyBatis-Plus 的 QueryWrapper 本身不引入 SQL 注入风险,但**一旦你用错方法或绕过参数绑定机制,风险立刻出现**。它不是“防注入组件”,而是一个条件构造容器——安全与否,全看你怎么填。
为什么 eq/like 等内置方法默认安全
这些方法(如 eq、like、between、in)底层统一走 JDBC PreparedStatement 参数绑定,等价于 MyBatis 的 #{} 行为:
- 参数值不会参与 SQL 解析,只作为数据传入驱动
-
queryWrapper.like("name", "admin' OR '1'='1")→ 实际执行的是WHERE name LIKE ?,参数值被原样当作字符串匹配,不会触发逻辑绕过 - 即使用户输入含
%、_或单引号,只要没手动拼接进 SQL 字符串,就不会破坏语义或结构
apply 方法是唯一高频引爆点
apply 是 Wrapper 中唯一允许插入原始 SQL 片段的入口,也是绝大多数线上注入漏洞的源头:
- ❌ 危险写法:
queryWrapper.apply("status = '" + statusParam + "'")—— 字符串拼接直接绕过预编译 - ✅ 安全写法:
queryWrapper.apply("status = {0}", statusParam)——{0}触发参数绑定,等同于? - ⚠️ 注意:
{0}只对参数值生效;字段名、表名、ORDER BY方向等结构部分不能用占位符,必须白名单校验 - 比如
queryWrapper.apply("ORDER BY {0} DESC", sortField)不会生效,甚至可能报错或被忽略
动态字段名(如排序、查询字段)必须白名单校验
Wrapper 对字段名不做任何过滤,orderByAsc(sortField) 或 select(sortField) 中的 sortField 是直接拼进 SQL 的:
- 前端传
sort=update_time; DROP TABLE user;--→ 最终生成ORDER BY update_time; DROP TABLE user;-- - 必须显式限制:
if (!List.of("price", "create_time", "sales").contains(sortField)) { throw new IllegalArgumentException(); } - 更稳妥的方式是用
LambdaQueryWrapper+ 方法引用:lambdaWrapper.orderByAsc(User::getPrice),彻底从源头规避字符串字段名
inSql 和 last 方法同样危险,规则一致
inSql 和 last 本质和 apply 同类,都属于“原始 SQL 插入口”:
-
queryWrapper.inSql("id", "SELECT goods_id FROM t_goods WHERE name LIKE '%" + keyword + "%'")—— 高危,完全拼接 - 应改写为:
queryWrapper.inSql("id", "SELECT goods_id FROM t_goods WHERE name LIKE {0}", "%" + keyword + "%"),且仅当子查询逻辑可控时才用 - 更推荐:把子查询拆成两步,先查出
goodsIdList,再用in方法传集合 —— 完全走参数绑定 -
last同理,避免last("LIMIT " + limit),改用分页插件或Page对象
真正容易被忽略的不是“怎么写安全”,而是误以为“用了 Wrapper 就自动安全”。只要涉及字符串拼接、结构动态化、或绕过 eq/like 等标准方法,你就已经站在注入边界上了。











