别再写硬编码了!MyBatis-Plus的apply方法,这样用才安全又灵活(附日期查询实战)
别再写硬编码了!MyBatis-Plus的apply方法,这样用才安全又灵活(附日期查询实战)
在Java开发中,动态SQL拼接是个绕不开的话题。记得刚入行时,我为了赶进度直接在代码里拼接SQL字符串,结果上线后遭遇了SQL注入攻击,差点酿成大祸。那次教训让我深刻认识到:字符串拼接不是编程,而是埋雷。今天我们就来聊聊MyBatis-Plus中那个被低估的apply方法——它可能是你代码安全最后的防线。
1. 为什么我们需要告别硬编码
硬编码SQL就像把数据库密码写在代码注释里——看似方便,实则危险。我曾见过一个经典案例:开发者为了查询某天的用户数据,直接写出了这样的代码:
String sql = "date_format(birthday,'%Y-%m-%d') = '" + date + "'"; wrapper.apply(sql);当date参数来自用户输入时,攻击者只需传入' OR '1'='1这样的值,就能轻松获取全表数据。而使用参数化替换的apply方法后:
wrapper.apply("date_format(birthday,'%Y-%m-%d') = {0}", date);MyBatis-Plus会自动将参数预处理为预编译语句,从根本上杜绝注入风险。这两种方式的底层SQL对比如下:
| 方式 | 生成SQL示例 | 安全性 |
|---|---|---|
| 硬编码 | date_format(birthday,'%Y-%m-%d') = '2023-01-01' | 高危 |
| 参数化 | date_format(birthday,'%Y-%m-%d') = ? | 安全 |
2. apply方法的实战应用技巧
2.1 日期处理的正确姿势
日期查询是最容易踩坑的场景。假设我们需要查询2023年国庆节当天的用户:
// 错误示范(直接拼接) wrapper.apply("DATE(birthday) = '2023-10-01'"); // 正确做法(参数化) wrapper.apply("DATE(birthday) = {0}", LocalDate.of(2023, 10, 1));更复杂的场景比如查询最近30天活跃用户:
wrapper.apply("last_login_time BETWEEN {0} AND {1}", LocalDateTime.now().minusDays(30), LocalDateTime.now());2.2 动态条件组合的艺术
apply的condition参数让动态SQL变得优雅。比如根据权限过滤数据:
boolean isAdmin = getCurrentUser().isAdmin(); wrapper.apply(!isAdmin, "department_id = {0}", userDeptId);当isAdmin为false时才会添加部门过滤条件。这种写法比在service层写if判断更清晰。
3. 高级应用:数据库函数安全调用
3.1 字符串函数的安全使用
字符串拼接同样需要防范注入。比如模糊查询用户名:
// 危险方式 wrapper.apply("CONCAT('%', '" + name + "', '%')"); // 安全方式 wrapper.apply("CONCAT('%', {0}, '%')", name);3.2 JSON字段查询方案
现在很多项目使用JSON字段存储扩展属性。假设我们要查询profile字段中address.city为北京的用户:
wrapper.apply("JSON_EXTRACT(profile, '$.address.city') = {0}", "北京");不同数据库的JSON函数差异较大,但参数化原则不变:
| 数据库 | JSON查询函数 | 示例 |
|---|---|---|
| MySQL | JSON_EXTRACT | JSON_EXTRACT(column, '$.path') |
| PostgreSQL | ->> | column->>'path' |
| Oracle | JSON_VALUE | JSON_VALUE(column, '$.path') |
4. 性能优化与最佳实践
4.1 索引使用注意事项
虽然apply很强大,但滥用会影响索引效率。比如这样的写法:
wrapper.apply("YEAR(create_time) = {0}", 2023);会导致MySQL无法使用create_time上的索引。更好的方式是:
wrapper.between("create_time", LocalDateTime.of(2023, 1, 1, 0, 0), LocalDateTime.of(2023, 12, 31, 23, 59));4.2 复杂查询的拆解策略
遇到特别复杂的SQL时,不要试图用一个apply解决所有问题。比如多表关联查询:
// 不推荐 wrapper.apply("EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = {0})", 1); // 推荐使用join或分开查询 wrapper.inSql("id", "SELECT user_id FROM orders WHERE status = 1");实际项目中,我发现将复杂逻辑拆分为多个简单查询往往性能更好,代码也更易维护。
