Spring Boot 中 MyBatis-Plus 时间戳筛选 datetime 列全指南:从注解配置到条件构造,轻松实现时间维度数据查询

Spring Boot 中 MyBatis-Plus 时间戳筛选 datetime 列全指南:从注解配置到条件构造,轻松实现时间维度数据查询

在 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;

}

关键配置说明

  • Java 类型选择:推荐使用LocalDateTime(Java 8+),而非Date,前者线程安全且 API 更友好,支持年月日时分秒精确到纳秒,完美匹配数据库 datetime 类型。
  • 数据库字段类型:MySQL 推荐使用DATETIME(范围 1000-01-01 00:00:00 至 9999-12-31 23:59:59),而非TIMESTAMP(受时区影响且范围较小)。
  • 自动填充配置:通过fill = FieldFill.INSERT_UPDATE配合 MP 的自动填充器,可实现创建 / 更新时间的自动赋值,避免手动设置。

自动填充器配置(可选)

若需自动维护创建 / 更新时间,可添加如下配置类:

 
  

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 语句。

1. 日期范围筛选(常用场景)

查询某段时间内的数据(如近 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 getLast7DaysOrders() {

// 计算7天前的时间(LocalDateTime便于日期计算)

LocalDateTime sevenDaysAgo = LocalDateTime.now().minus(7, ChronoUnit.DAYS);

LocalDateTime now = LocalDateTime.now();

// 构造查询条件:create_time BETWEEN sevenDaysAgo AND now

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.between("create_time", sevenDaysAgo, now);

// 执行查询(MP自动转换为SQL的BETWEEN条件)

return orderMapper.selectList(queryWrapper);

}

}

2. 精确时间点筛选

查询某一具体时间的数据(如 2023 年 10 月 1 日创建的订单),使用eq方法:

 
  

// 查询2023-10-01 00:00:00创建的订单

@GetMapping("/orders/on-20231001")

public List getOrdersOnSpecificDay() {

// 构造精确时间点(注意时分秒需明确)

LocalDateTime targetTime = LocalDateTime.of(2023, 10, 1, 0, 0, 0);

QueryWrapper queryWrapper = new QueryWrapper<>();

queryWrapper.eq("create_time", targetTime);

return orderMapper.selectList(queryWrapper);

}

3. 大于 / 小于时间筛选

查询某时间之后或之前的数据(如今天 0 点后创建的订单),使用gt(大于)、lt(小于)、ge(大于等于)、le(小于等于)方法:

 
  

// 查询今天0点之后创建的订单

@GetMapping("/orders/today")

public List getTodayOrders() {

// 今天0点0分0秒

LocalDateTime todayStart = LocalDateTime.now()

.withHour(0)

.withMinute(0)

.withSecond(0)

.withNano(0);

QueryWrapper queryWrapper =new QueryWrapper<>();

// create_time >= todayStart

queryWrapper.ge("create_time", todayStart);

return orderMapper.selectList(queryWrapper);

}

三、高级筛选场景:动态条件与函数调用

实际业务中,时间筛选往往不是单一条件,需结合动态参数(如前端传入的开始 / 结束时间)或数据库函数(如按日期部分筛选)。MP 的条件构造器支持动态拼接和函数调用,满足复杂场景需求。

1. 动态时间条件(前端传入可选时间范围)

当前端可能传入开始时间、结束时间或两者都传入时,需动态构造条件:

 
  

// 动态时间范围查询(支持开始时间、结束时间可选)

@GetMapping("/orders/dynamic-range")

public List getOrdersByDynamicRange(

@RequestParam(required = false) LocalDateTime startTime,

@RequestParam(required = false) LocalDateTime endTime) {

QueryWrapper queryWrapper = new QueryWrapper<>();

// 若传入开始时间,则添加>=条件

if (startTime != null) {

queryWrapper.ge("create_time", startTime);

}

// 若传入结束时间,则添加<=条件

if (endTime != null) {

queryWrapper.le("create_time", endTime);

}

return orderMapper.selectList(queryWrapper);

}

Spring Boot 中 MyBatis-Plus 时间戳筛选 datetime 列全指南:从注解配置到条件构造,轻松实现时间维度数据查询_第1张图片

2. 按日期部分筛选(忽略时间只比较日期)

查询某一天的数据但不限制具体时间(如 2023-10-01 全天的订单),需忽略时间部分,仅比较年月日:

 
  

// 查询2023-10-01全天的订单(忽略时分秒)

@GetMapping("/orders/on-day-ignore-time")

public List getOrdersOnDayIgnoreTime() {

LocalDateTime targetDayStart = LocalDateTime.of(2023, 10, 1, 0, 0, 0);

LocalDateTime targetDayEnd = LocalDateTime.of(2023, 10, 1, 23, 59, 59);

QueryWrapper queryWrapper = new QueryWrapper<>();

// create_time >= 当天0点 AND create_time <= 当天23:59:59

queryWrapper.between("create_time", targetDayStart, targetDayEnd);

return orderMapper.selectList(queryWrapper);

}

3. 结合数据库函数筛选(如按月 / 按季度)

使用 MP 的apply方法调用数据库函数(如 MySQL 的DATE_FORMAT),实现按月份、季度等维度筛选:

 
  

// 查询2023年10月份的所有订单(使用MySQL的DATE_FORMAT函数)

@GetMapping("/orders/in-october-2023")

public List getOrdersInOctober2023() {

QueryWrapper queryWrapper = new 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 注入风险,比直接拼接字符串更安全。

四、时间筛选的常见陷阱与解决方案

时间筛选过程中,因时区、精度、格式转换等问题易出现查询结果不符合预期的情况,需特别注意以下几点:

陷阱 1:时区不一致导致时间偏差

现象:Java 代码中设置的时间是2023-10-01 00:00:00,但查询结果包含前一天的数据。

成因:数据库时区与应用服务器时区不一致(如数据库使用 UTC,应用使用 GMT+8)。

解决方案

  1. 统一时区配置(推荐数据库与应用均使用 GMT+8)。
  1. 连接 URL 中指定时区(MySQL 为例):
 
  

spring:

datasource:

url: jdbc:mysql://localhost:3306/order_db?serverTimezone=Asia/Shanghai

陷阱 2:LocalDateTime 与 Date 混用导致转换错误

现象:实体类使用Date类型,查询时出现InvalidDataAccessApiUsageException。

成因:MP 对Date类型的自动转换支持不如LocalDateTime完善,易出现格式不兼容。

解决方案

  • 统一使用LocalDateTime(Java 8 + 推荐),避免Date类型。
  • 若必须使用Date,需指定类型转换器:
 
  

@TableField(value = "create_time", typeHandler = com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler.class)

private Date createTime;

陷阱 3:时间精度不匹配导致筛选失效

现象:查询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 getUnpaidLargeAmountOrders() {

// 计算30天前的时间

LocalDateTime thirtyDaysAgo = LocalDateTime.now().minus(30, ChronoUnit.DAYS);

QueryWrapper queryWrapper = new 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 自动填充,无需手动处理类型转换,实现了安全高效的多条件查询。

六、时间筛选的性能优化建议

时间筛选若处理不当,易导致全表扫描(尤其在大数据量场景),需结合索引与查询优化提升性能。

1. 索引优化

  • 为时间列创建索引(如create_time):
 
  

CREATE INDEX idx_order_create_time ONorder(create_time);

  • 联合索引(适用于常与时间条件组合的字段):
 
  

-- 若频繁按create_time和status查询,创建联合索引

CREATE INDEX idx_order_status_create_time ON order(status, create_time);

2. 查询优化

  • 避免在时间列上使用函数(如DATE_FORMAT(create_time, '%Y-%m')),会导致索引失效。
  • 范围查询时,尽量使用BETWEEN而非>=+<=(部分数据库对BETWEEN优化更好)。
  • 分页查询时,结合limit减少返回数据量:
 
  

// 分页查询近7天订单(避免一次性返回大量数据)

IPage page = new Page<>(1, 10); // 第1页,每页10条

IPage result = orderMapper.selectPage(page, queryWrapper);

结语:时间筛选的核心原则

利用 MyBatis-Plus 实现时间筛选的核心,在于理解 "注解配置 - 条件构造 - 类型转换" 的协同机制。掌握以下原则可高效完成各类时间筛选需求:

  • 类型统一原则:实体类优先使用LocalDateTime,数据库使用带时区的 datetime 类型,避免类型混用导致的转换问题。
  • 条件明确原则:范围查询用between,精确时间用eq,动态条件用条件判断拼接,使代码意图清晰。
  • 性能优先原则:时间列必须创建索引,避免在筛选条件中使用函数,确保查询高效执行。

结合本文提供的配置模板与查询示例,开发者可快速实现从简单日期筛选到复杂动态条件的各类需求,将时间处理的复杂度交由 MyBatis-Plus 自动处理,聚焦于业务逻辑的实现。对于大数据量场景,建议结合 MP 的分页查询与索引优化,平衡查询效率与业务需求。

你可能感兴趣的:(spring,boot,mybatis,后端)