高级搜索时间完善(7.23)
一、高级搜索时间
之前使用将params的endActualArrivalTime+1天-1秒来解决高级搜索时间问题,下面换一种解决方式。
(1)点击搜索按钮后,查看请求可知,前端发送的时间是年月日,不带时分秒。

(2)数据库中的create_time是带时分秒的。

(3)过程分析:
首先,为什么前端发送的时间格式是年月日,不带时分秒的呢?
这是因为前端vue中的组件配置好了时间格式。
<el-form-item label="创建时间">
<el-date-picker
v-model="daterangeCreateTime"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
前端日期选择器里选了要查询的日期,双向数据绑定到daterangeCreateTime: []中。
- 若前端没传日期,就直接把queryParams作为查询参数发送到后端,这个queryParams是不包含日期属性的,所以根本就不会按时间查询。
- 若前端传了日期,就截取daterangeCreateTime中的开始时间和结束时间,并封装到queryParams.params中,相当于在queryParams追加了一个叫params的属性,里面包含了开始时间和结束时间。最后把queryParams作为查询参数发送到后端。
/** 查询审核列表列表 */
getList() {
this.loading = true
this.queryParams.params = {}
if (null != this.daterangeCreateTime && '' != this.daterangeCreateTime) {
this.queryParams.params["beginCreateTime"] = this.daterangeCreateTime[0]
this.queryParams.params["endCreateTime"] = this.daterangeCreateTime[1]
}
listCarPackageAudit(this.queryParams).then(response => {
this.carPackageAuditList = response.rows
this.total = response.total
this.loading = false
})
},
前端发送 JSON 请求,后端 Controller 用 BusCarPackageAudit 对象接收。BusCarPackageAudit 继承了BaseEntity中的params字段,且该字段加了@JsonInclude(JsonInclude.Include.NON_EMPTY)注解。
这个注解不关心时间格式,它只关心“要不要把这个字段发给前端”,如果 params 这个 Map 是空的,后端返回 JSON 时就不要带上它,保持返回数据干净清爽。
所以此时 beginCreateTime 和 endCreateTime 只是 Map 里的普通字符串(String),没有任何注解去格式化它们。接收格式还是年月日。但是返回是把查询到的时间数据映射到create_time 字段,该字段是带@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解的,所以返回格式是年月日时分秒。
XML 里通过 #{params.beginCreateTime} 取出了字符串 "2026-07-20"。
因为数据库里的 create_time 是带时分秒的(比如 2026-07-23 14:30:00),直接比较会查不到数据。
所以 SQL 里用了 DATE_FORMAT(create_time,'%Y-%m-%d'),先把数据库时间的时分秒“砍掉”,变成 2026-07-23,然后再去和前端传来的 "2026-07-23" 做比较,完美匹配!
最后把从数据库中查出来的原始数据,映射到当前实体对象的 createTime 属性中,返回给前端。createTime 字段默认是带了 @JsonFormat 注解的(@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"))。所以后端会把查询到的数据按照年月日时分秒的格式返回给前端展示。
(4)解决方案:
加一条if标签
<if test="params.beginCreateTime != null and params.endCreateTime != null">
and DATE_FORMAT(create_time,'%Y-%m-%d') between #{params.beginCreateTime} and #{params.endCreateTime}
</if>
<select id="selectBusCarPackageAuditList" parameterType="BusCarPackageAudit" resultMap="BusCarPackageAuditResult">
<include refid="selectBusCarPackageAuditVo"/>
<where>
<if test="serviceItemId != null "> and service_item_id = #{serviceItemId}</if>
<if test="serviceItemName != null and serviceItemName != ''"> and service_item_name like concat('%', #{serviceItemName}, '%')</if>
<if test="serviceItemInfo != null and serviceItemInfo != ''"> and service_item_info = #{serviceItemInfo}</if>
<if test="serviceItemPrice != null "> and service_item_price = #{serviceItemPrice}</if>
<if test="instanceId != null and instanceId != ''"> and instance_id = #{instanceId}</if>
<if test="creatorId != null and creatorId != ''"> and creator_id = #{creatorId}</if>
<if test="info != null and info != ''"> and info = #{info}</if>
<if test="status != null "> and status = #{status}</if>
<if test="params.beginCreateTime != null and params.endCreateTime != null">
and DATE_FORMAT(create_time,'%Y-%m-%d') between #{params.beginCreateTime} and #{params.endCreateTime}
</if>
</where>
</select>
注意:
①SQL 中的 DATE_FORMAT 只作用于“查询条件”。这里的 DATE_FORMAT 仅仅是一个过滤器。它的作用是在数据库执行查询时,临时把数据库里的时间“砍掉”时分秒,用来和前端传来的纯日期做比较,从而筛选出符合条件的数据。它绝对不会修改数据库里原本存储的数据。
and DATE_FORMAT(create_time,'%Y-%m-%d') between #{params.beginCreateTime} and #{params.endCreateTime}
②SELECT 查询出来的依然是原始数据。虽然我们用 DATE_FORMAT 过滤了数据,但我们的 SQL 语句(通过 <include refid="selectBusCarPackageAuditVo"/>)在 SELECT 返回结果时,取的依然是数据库里的原始字段。
③Java 字段接收与 @JsonFormat 格式化。当 MyBatis 把这个完整的 2026-07-23 14:30:00 映射到 Java 实体类的 Date createTime 字段时,Java 对象里存的就是带时分秒的完整时间。最后,当后端把这个 Java 对象转换成 JSON 返回给前端时,触发了 BaseEntity 里的 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 注解。这个注解的作用就是“把 Java 对象里的时间,按照指定的格式输出”。因为 Java 对象里本来就是带时分秒的,所以格式化输出后,前端收到的自然还是 "2026-07-23 14:30:00"。
补充:
反面教材
假设你在自己的实体类里,重新定义了 createTime 字段,并且加上了注解(强烈不推荐子类强行覆盖父类规则):
public class BusCarPackageAudit extends BaseEntity {
// 自己重新定义了字段,并加了注解
@JsonFormat(pattern = "yyyy-MM-dd")
private Date createTime;
}
这时候会发生“字段覆盖”(或者叫遮蔽)。
- 当 Spring 框架扫描这个对象时,它会优先使用子类自己定义的字段和注解。
- 父类
BaseEntity里的那个createTime就会被忽略。 - 结果:前端收到的时间格式,将完全按照你子类里写的
"yyyy-MM-dd"来返回,父类的"yyyy-MM-dd HH:mm:ss"就失效了。
二、@JsonFormat 和 @DateTimeFormat 的区别是什么?
@JsonFormat 是给 JSON 用的(接口交互),@DateTimeFormat 是给 URL/表单用的(参数传递)。
| 特性 | @JsonFormat |
@DateTimeFormat |
|---|---|---|
| 出身背景 | Jackson 库 (Spring Boot 默认 JSON 处理器) | Spring Framework (Web 数据绑定) |
| 作用对象 | JSON 字符串 ↔ Java 对象 | URL 参数 / 表单字符串 ↔ Java 对象 |
| 生效时机 | 请求体解析时、响应序列化时 | 控制器方法参数绑定时 |
| 典型场景 | 前后端分离开发 (axios, fetch) |
传统表单提交、GET 请求参数 |
| 配置位置 | 实体类字段上 | Controller 方法参数上 |
(1)@JsonFormat:JSON 数据的"翻译官"
-
严谨定义:它属于 Jackson 库的注解。当 Spring MVC 使用
MappingJackson2HttpMessageConverter处理 HTTP 消息时,该注解负责将 JSON 字符串与 Java 日期对象进行双向转换。 -
通俗理解:它是专门给 JSON 格式 定制的。
- 入参时:前端发来的 JSON 里时间是
"2026-07-23 10:00:00",后端收到后自动转成 Java 的Date对象。 - 出参时:后端把
Date对象返回给前端,自动转成"2026-07-23 10:00:00"这种好看的字符串。
- 入参时:前端发来的 JSON 里时间是
-
代码示例:
// 放在实体类字段上 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime;
(2)@DateTimeFormat:URL/表单参数的"接收器"
-
严谨定义:它属于 Spring Framework 的注解。当 Spring MVC 进行 Web 数据绑定(Data Binding)时,例如解析 URL 中的查询参数或表单字段,该注解负责将字符串转换为日期类型。
-
通俗理解:它是专门给 浏览器地址栏 或 传统表单 定制的。
- 如果你用 GET 请求:
http://api.com/list?startTime=2026-07-23,这个参数不是 JSON,而是 URL 的一部分,必须用它来告诉后端怎么把这个字符串变成日期。
- 如果你用 GET 请求:
-
代码示例:
// 放在 Controller 方法的参数上 public Result list(@DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime) { // ... }
浙公网安备 33010602011771号