在 MyBatis Plus 中,QueryWrapper 的 apply 方法用于 直接注入原生 SQL 片段 到查询条件中(通常是 WHERE 子句)。
当 MyBatis Plus 提供的标准方法(如 eq、gt、like 等)无法满足复杂查询需求时,可以使用 apply 来编写自定义的 SQL 逻辑。
一、核心作用
apply 允许你在 Wrapper 中拼接任意的 SQL 条件,主要用于以下场景:
- 使用数据库函数:如
DATE_FORMAT、UNIX_TIMESTAMP、FIND_IN_SET等。 - 字段与字段比较:标准方法通常是
字段 = 值,而apply可以实现字段 A > 字段 B。 - 特殊语法:如 JSON 查询、全文检索、空间几何查询等。
二、使用示例
1. 使用数据库函数
查询创建日期为 "2023-10-01" 的用户(忽略时间部分)。
QueryWrapper<User> wrapper = new QueryWrapper<>();
// SQL: WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2023-10-01'
wrapper.apply("DATE_FORMAT(create_time, '%Y-%m-%d') = {0}", "2023-10-01");
List<User> users = userMapper.selectList(wrapper);
2. 字段与字段比较
查询 start_time 早于 end_time 的记录。
QueryWrapper<Order> wrapper = new QueryWrapper<>();
// SQL: WHERE start_time < end_time
wrapper.apply("start_time < end_time");
List<Order> orders = orderMapper.selectList(wrapper);
3. 复杂逻辑(FIND_IN_SET)
查询标签包含 "1" 的记录(MySQL 特有)。
QueryWrapper<Product> wrapper = new QueryWrapper<>();
// SQL: WHERE FIND_IN_SET('1', tags)
wrapper.apply("FIND_IN_SET({0}, tags)", "1");
List<Product> products = productMapper.selectList(wrapper);
三、⚠️ 重要安全警告(SQL 注入)
apply 是 SQL 注入的高发区! 必须严格遵守以下规则:
✅ 正确写法(使用占位符)
使用 {0}, {1} 作为占位符,并将用户输入作为参数传递。MyBatis 会自动进行预编译处理(?),防止注入。
// 安全:username 会被当作参数处理
String username = userInput;
wrapper.apply("name = {0}", username);
// 生成 SQL: WHERE name = ?
❌ 错误写法(直接拼接)
绝对禁止将用户输入直接拼接到 SQL 字符串中。
// 危险!如果 userInput 是 "admin' OR '1'='1",会导致全表泄露
String username = userInput;
wrapper.apply("name = '" + username + "'");
// 生成 SQL: WHERE name = 'admin' OR '1'='1' (注入成功)
四、apply 与其他方法的区别
| 方法 | 作用 | 示例 | 适用场景 |
|---|---|---|---|
eq / gt | 标准条件 | .eq("name", "张三") | 常规字段等于/大于某值 |
apply | 注入 SQL 片段 | .apply("DATE_FORMAT(time, '%Y') = {0}", 2023) | 函数、字段对比、特殊语法 |
last | 追加到 SQL 末尾 | .last("LIMIT 1") | 强行添加 LIMIT、ORDER BY 等(优先级最高) |
func | 条件复用 | .func(consumer) | 抽取公共条件逻辑 |
注意:
apply生成的内容会位于WHERE子句中,而last是直接拼接到整个 SQL 语句的最后。
五、LambdaQueryWrapper 中的 apply
LambdaQueryWrapper 也可以使用 apply,但要注意 apply 内部无法使用方法引用,只能写字符串列名。
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 注意:这里不能写 User::getCreateTime,只能写字符串 "create_time"
wrapper.apply("DATE_FORMAT(create_time, '%Y-%m-%d') = {0}", "2023-01-01");
缺点:在 LambdaWrapper 中使用 apply 会失去列名的类型安全检查(如果数据库列名重构,这里不会报错,运行时会报错)。
六、总结与建议
- 优先使用标准方法:能用
eq、like、in解决的,不要用apply,以保证代码可读性和数据库无关性。 - 必须防注入:使用
apply时,务必使用{0},{1}占位符传递参数,严禁字符串拼接。 - 注意数据库兼容:
apply中的 SQL 是原生的,切换数据库(如 MySQL 转 Oracle)时可能需要修改代码。 - 替代方案:如果
apply逻辑过于复杂,建议直接写 XML 自定义 SQL,更易于维护。
一句话总结:apply 是 MyBatis Plus 留给你的“后门”,用于处理标准 API 搞不定的特殊 SQL 逻辑,但使用时务必小心 SQL 注入风险。
177

被折叠的 条评论
为什么被折叠?



