MyBatis-Plus 实现时间范围查询的正确姿势与安全规范

凌晨两点半
凌晨两点半
发布时间:2026-10-15 | 5人浏览过
原创

MyBatis-Plus 实现时间范围查询主要有两种路径:针对仅需按“日”聚合数据的场景,推荐使用 apply 方法结合数据库日期函数进行安全参数化查询;针对精确到“时分秒”的场景,直接使用 ge 和 le 方法比对 Date 对象更为高效。此外,前端传参格式必须通过 @DateTimeFormat 注解与后端接收类型严格匹配,否则会导致解析异常。

结果与目标:确定查询精度与工具选择

在处理后台管理系统或数据报表的时间筛选功能时,首先需明确数据库存储的精度与前端展示的粒度。若数据库存储了完整的时分秒(如 yyyy-MM-dd HH:mm:ss),而业务仅需按“天”统计,则需在 SQL 层截断时间部分;若需精确筛选某一时刻前后的数据,则可直接进行数值比较。选择合适的查询方式不仅能提升代码可读性,更是防止 SQL 注入的关键防线。

第1步:准备条件配置日期格式化规则

在进行任何查询之前,必须确保前后端传递的时间字符串格式与后端 Java 对象的映射关系正确,否则会导致数据绑定失败抛出异常。

  • 目的:统一时间字符串解析标准,避免 IllegalArgumentException。
  • 操作入口:后端 DTO/VO 类定义处。
  • 动作:
    在接收时间参数的字段上添加 @DateTimeFormat 注解。
    指定 pattern 属性以明确期望的输入格式,例如 "yyyy-MM-dd HH:mm:ss"。
  • 重要参数:导入包为 org.springframework.format.annotation.DateTimeFormat,这是 Spring Framework 原生支持,无需额外依赖。
  • 完成状态:当 Controller 接收到形如 2022-09-15 20:00:00 的请求体时,能自动转换为 java.util.Date 或 java.time 类型对象。若前端传递 ISO8601 格式(如 2022-09-13T16:00:00.000Z)而未配置对应格式,将导致 400 错误。
public class ServiceItemListDto implements Serializable {

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date startTime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date endTime;

    // Getter 和 Setter 省略
}

第2步:实施高精度范围查询(ge/le 方式)

当需要精确匹配包含时分秒的时间点,或者数据库字段本身就是 TIMESTAMP/DATETIME 且无需截断时,使用 MyBatis-Plus 提供的区间比较方法是最佳实践。这种方式语义清晰,性能优于数据库函数计算。

  • 目的:构建基于日期对象的大于等于和小于等于查询条件。
  • 入口:Service 层逻辑处理区,获取前端传入的开始时间和结束时间。
  • 动作:使用 LambdaQueryWrapper,配合 Optional 判空后,调用 ge (greater than or equal) 和 le (less than or equal) 方法。
  • 可观察状态:生成的 SQL 片段类似于 create_time >= ? AND create_time <= ?,其中 ? 由 MyBatis 安全填充日期参数。
LambdaQueryWrapper<ObsConsultationRequest> qw = new LambdaQueryWrapper<>();
// 如果有开始时间,添加大于等于条件
Optional.ofNullable(request.getStartRequestDatetime()).ifPresent(
        date -> qw.ge(ObsConsultationRequest::getRequestDatetime, date));
// 如果有结束时间,添加小于等于条件
Optional.ofNullable(request.getEndRequestDatetime()).ifPresent(
        date -> qw.le(ObsConsultationRequest::getRequestDatetime, date));

第3步:实施低精度日期截断查询(apply 方式)

当数据库存储了具体时间,但业务逻辑只关心“哪一天”(忽略时分秒)时,需要在 SQL 层面使用数据库内置函数(如 MySQL 的 DATE_FORMAT)进行转换。此方式极易产生 SQL 注入漏洞,必须严格遵循参数化原则。

  • 目的:在查询条件中对数据库字段应用日期格式化函数,并安全绑定动态参数。
  • 关键风险提醒:严禁使用字符串拼接直接将变量插入 SQL 语句!这会导致严重的 SQL 注入风险。

反例(危险做法):
直接使用字符串拼接将用户输入嵌入 SQL 语句中,即使做了非空判断,仍可能被恶意构造的攻击 payload 绕过。

// ❌ 错误示范:字符串拼接导致 SQL 注入风险
queryWrapper.apply(StrUtil.isNotBlank(serviceItemListDto.getStartTime()),
    "date_format(create_time,'%Y-%m-%d') >= date_format('" + serviceItemListDto.getStartTime() + "','%Y-%m-%d')");

正例(安全做法):
使用 apply 方法的三个重载版本: apply(boolean condition, String applySql, Object... params)。第一个参数控制条件是否生效,第二个参数是带 {index} 占位符的 SQL 片段,后续参数即为实际绑定的值。

// ✅ 正确示范:使用占位符 {0} 传入参数
queryWrapper.apply(serviceItemListDto.getStartTime() != null,
    "date_format(create_time,'%Y-%m-%d') >= date_format({0},'%Y-%m-%d')",
    serviceItemListDto.getStartTime())
.and()
.apply(serviceItemListDto.getEndTime() != null,
    "date_format(create_time,'%Y-%m-%d') <= date_format({0},'%Y-%m-%d')",
    serviceItemListDto.getEndTime());

传参类型与请求协议兼容性

MyBatis-Plus 的上述查询逻辑不依赖于 HTTP 请求的具体方式。无论是 GET 请求通过 URL Param 传递,还是 POST 请求通过 JSON Body 传递,只要后端能正确解析出 startTime 和 endTime 对象,查询构建逻辑保持不变。

通常建议在后端接口定义中保留明确的注解说明,以便前端开发者理解所需的数据结构:

@ApiModelProperty(value = "开始时间")
private String startTime;
@ApiModelProperty(value = "结束时间 ")
private String endTime;

总结与排错指南

  • 格式不匹配报错:如果控制台或日志出现 Invalid format 或 Binder error,请检查 @DateTimeFormat(pattern = "...") 中的模式是否与前端实际传输的字符串完全一致(包括空格、分隔符等)。
  • 时区问题:Java 的 Date 对象本身不包含时区信息(仅表示 UTC 毫秒数),但在序列化和反序列化过程中可能涉及 JVM 默认时区或 JDBC 驱动时区设置。若发现时间偏差 8 小时或更多,请检查数据库连接串与时区配置。
  • 性能考量:使用 apply 配合数据库函数会导致索引失效(Index Scan 全表扫描),对于千万级以上的数据表,建议仅在必要展示层级使用日期截断,或在数据库层维护单独的“日期”字段并建立索引。

以上就是 MyBatis-Plus 时间范围查询方式及安全实现的详细内容,更多关于 Java 后端开发中 SQL 注入防范与 ORM 框架最佳实践的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表