在 Spring Boot 开发中,基于时间维度筛选数据是常见需求,如查询近 7 天订单、统计当月新增用户等。MyBatis-Plus(简称 MP)提供了便捷的时间处理机制,通过注解配置与条件构造器,可轻松实现 Java 时间戳与数据库 datetime 类型的映射筛选。本文将系统讲解 MP 处理时间筛选的完整方案,从实体类注解配置到多样化查询构造,结合实战案例说明日期范围、精确时间、动态条件等场景的实现方法,帮助开发者避开时间格式转换陷阱,高效完成时间维度的数据筛选。
数据库 datetime 类型(如 MySQL 的 DATETIME、Oracle 的 DATE)与 Java 时间类型(LocalDateTime、Date)的正确映射,是实现时间筛选的前提。MP 通过@TableField注解及类型转换器,自动完成两者的转换,无需手动处理格式问题。
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Order {
@TableId
private Long id;
private String orderNo;
private BigDecimal amount;
// 订单创建时间(映射数据库datetime列)
@TableField(value = "create_time") // 对应数据库字段名
private LocalDateTime createTime;
// 订单更新时间(自动填充+时间戳)
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
若需自动维护创建 / 更新时间,可添加如下配置类:
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时自动填充创建时间
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
// 更新时自动填充更新时间
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
配置后,新增订单时createTime和updateTime会自动设为当前时间,更新时updateTime自动刷新,无需在代码中显式设置。
MP 的QueryWrapper(条件构造器)提供了丰富的时间筛选方法,通过链式调用可轻松构造>、<、>=、<=、BETWEEN等条件,无需编写 SQL 语句。
查询某段时间内的数据(如近 7 天订单、当月新增用户),使用between方法:
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
@RestController
public class OrderController {
@Autowired
private OrderMapper orderMapper;
// 查询近7天的订单
@GetMapping("/orders/last7days")
public List
// 计算7天前的时间(LocalDateTime便于日期计算)
LocalDateTime sevenDaysAgo = LocalDateTime.now().minus(7, ChronoUnit.DAYS);
LocalDateTime now = LocalDateTime.now();
// 构造查询条件:create_time BETWEEN sevenDaysAgo AND now
QueryWrapper
queryWrapper.between("create_time", sevenDaysAgo, now);
// 执行查询(MP自动转换为SQL的BETWEEN条件)
return orderMapper.selectList(queryWrapper);
}
}
查询某一具体时间的数据(如 2023 年 10 月 1 日创建的订单),使用eq方法:
// 查询2023-10-01 00:00:00创建的订单
@GetMapping("/orders/on-20231001")
public List
// 构造精确时间点(注意时分秒需明确)
LocalDateTime targetTime = LocalDateTime.of(2023, 10, 1, 0, 0, 0);
QueryWrapper
queryWrapper.eq("create_time", targetTime);
return orderMapper.selectList(queryWrapper);
}
查询某时间之后或之前的数据(如今天 0 点后创建的订单),使用gt(大于)、lt(小于)、ge(大于等于)、le(小于等于)方法:
// 查询今天0点之后创建的订单
@GetMapping("/orders/today")
public List
// 今天0点0分0秒
LocalDateTime todayStart = LocalDateTime.now()
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
QueryWrapper
// create_time >= todayStart
queryWrapper.ge("create_time", todayStart);
return orderMapper.selectList(queryWrapper);
}
实际业务中,时间筛选往往不是单一条件,需结合动态参数(如前端传入的开始 / 结束时间)或数据库函数(如按日期部分筛选)。MP 的条件构造器支持动态拼接和函数调用,满足复杂场景需求。
当前端可能传入开始时间、结束时间或两者都传入时,需动态构造条件:
// 动态时间范围查询(支持开始时间、结束时间可选)
@GetMapping("/orders/dynamic-range")
public List
@RequestParam(required = false) LocalDateTime startTime,
@RequestParam(required = false) LocalDateTime endTime) {
QueryWrapper
// 若传入开始时间,则添加>=条件
if (startTime != null) {
queryWrapper.ge("create_time", startTime);
}
// 若传入结束时间,则添加<=条件
if (endTime != null) {
queryWrapper.le("create_time", endTime);
}
return orderMapper.selectList(queryWrapper);
}
查询某一天的数据但不限制具体时间(如 2023-10-01 全天的订单),需忽略时间部分,仅比较年月日:
// 查询2023-10-01全天的订单(忽略时分秒)
@GetMapping("/orders/on-day-ignore-time")
public List
LocalDateTime targetDayStart = LocalDateTime.of(2023, 10, 1, 0, 0, 0);
LocalDateTime targetDayEnd = LocalDateTime.of(2023, 10, 1, 23, 59, 59);
QueryWrapper
// create_time >= 当天0点 AND create_time <= 当天23:59:59
queryWrapper.between("create_time", targetDayStart, targetDayEnd);
return orderMapper.selectList(queryWrapper);
}
使用 MP 的apply方法调用数据库函数(如 MySQL 的DATE_FORMAT),实现按月份、季度等维度筛选:
// 查询2023年10月份的所有订单(使用MySQL的DATE_FORMAT函数)
@GetMapping("/orders/in-october-2023")
public List
QueryWrapper
// 调用数据库函数:DATE_FORMAT(create_time, '%Y-%m') = '2023-10'
queryWrapper.apply("DATE_FORMAT(create_time, '%Y-%m') = {0}", "2023-10");
return orderMapper.selectList(queryWrapper);
}
apply方法支持参数占位符({0}),避免 SQL 注入风险,比直接拼接字符串更安全。
时间筛选过程中,因时区、精度、格式转换等问题易出现查询结果不符合预期的情况,需特别注意以下几点:
现象:Java 代码中设置的时间是2023-10-01 00:00:00,但查询结果包含前一天的数据。
成因:数据库时区与应用服务器时区不一致(如数据库使用 UTC,应用使用 GMT+8)。
解决方案:
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db?serverTimezone=Asia/Shanghai
现象:实体类使用Date类型,查询时出现InvalidDataAccessApiUsageException。
成因:MP 对Date类型的自动转换支持不如LocalDateTime完善,易出现格式不兼容。
解决方案:
@TableField(value = "create_time", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)
private Date createTime;
现象:查询eq("create_time", LocalDateTime.of(2023,10,1,0,0,0))无结果,但数据库存在该时间的数据。
成因:数据库 datetime 列包含毫秒级精度(如2023-10-01 00:00:00.123),而查询条件不含毫秒。
解决方案:
// 替代eq,使用范围查询包含毫秒差异
queryWrapper.ge("create_time", LocalDateTime.of(2023,10,1,0,0,0))
.lt("create_time", LocalDateTime.of(2023,10,1,0,0,1));
结合业务场景,实现 "查询近 30 天内金额大于 1000 元的未支付订单,按创建时间倒序排列" 的需求:
@GetMapping("/orders/unpaid-large-amount")
public List
// 计算30天前的时间
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minus(30, ChronoUnit.DAYS);
QueryWrapper
// 时间范围:create_time >= 30天前
queryWrapper.ge("create_time", thirtyDaysAgo)
// 金额条件:amount > 1000
.gt("amount", new BigDecimal("1000.00"))
// 状态条件:status = 0(未支付)
.eq("status", 0)
// 排序:按创建时间倒序
.orderByDesc("create_time");
return orderMapper.selectList(queryWrapper);
}
对应的生成 SQL 如下(MP 自动拼接):
SELECT id, order_no, amount, create_time, status
FROM order
WHERE create_time >= ? AND amount > ? AND status = ?
ORDER BY create_time DESC
参数值由 MP 自动填充,无需手动处理类型转换,实现了安全高效的多条件查询。
时间筛选若处理不当,易导致全表扫描(尤其在大数据量场景),需结合索引与查询优化提升性能。
CREATE INDEX idx_order_create_time ONorder(create_time);
-- 若频繁按create_time和status查询,创建联合索引
CREATE INDEX idx_order_status_create_time ON order(status, create_time);
// 分页查询近7天订单(避免一次性返回大量数据)
IPage
IPage
利用 MyBatis-Plus 实现时间筛选的核心,在于理解 "注解配置 - 条件构造 - 类型转换" 的协同机制。掌握以下原则可高效完成各类时间筛选需求:
结合本文提供的配置模板与查询示例,开发者可快速实现从简单日期筛选到复杂动态条件的各类需求,将时间处理的复杂度交由 MyBatis-Plus 自动处理,聚焦于业务逻辑的实现。对于大数据量场景,建议结合 MP 的分页查询与索引优化,平衡查询效率与业务需求。