更新时间相差8个小时

下面的java代码在updateFill方法里面生成的modifiedTime时间是当前时间是正确的,为什么到service层testCommonFieldAutoUpdate方法里面去更新的时候modifiedTime就差8个小时呢?代码如下所示:

@Slf4j

@Component

public class MpMetaObjectHandler implements MetaObjectHandler {

@Override

public void insertFill(MetaObject metaObject) {

//通用填充

this.setFieldValByName("createdTime", LocalDateTime.now(), metaObject);

//严格模式:根据更新策略填充

this.strictInsertFill(metaObject,"modifiedTime",LocalDateTime::now,LocalDateTime.class);

this.strictInsertFill(metaObject,"tenantNo",()-> HttpHeaderUtil.getHeaderValue("tenant-no"),String.class);

}

/**

* 使用mp的更新类api时会回调

* update user set address=?,modified_time=? where id=?

*/

@Override

public void updateFill(MetaObject metaObject) {

this.strictUpdateFill(metaObject, "modifiedTime", () ->

Date.from(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toInstant()), Date.class);

}

@Override

public MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {

Object obj = fieldVal.get();

if (Objects.nonNull(obj)) {

metaObject.setValue(fieldName, obj);

}

return this;

}

}

service层代码如下所示:

@Service

@Slf4j

public class UserRoadServiceImpl extends ServiceImpl<UserRoadMapper, UserDO> implements UserRoadService {

import cn.hutool.core.convert.Convert;

import cn.hutool.core.text.CharSequenceUtil;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import lombok.extern.slf4j.Slf4j;

import org.project.mapper.UserRoadMapper;

import org.project.roadjava.ramble.test.bean.entry.UserDO;

import org.project.roadjava.ramble.test.bean.req.user.AddUserRequest;

import org.project.roadjava.ramble.test.bean.req.user.UpdateUserRequest;

import org.project.roadjava.ramble.test.ex.BizEx;

import org.project.roadjava.ramble.test.service.UserRoadService;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

import java.util.List;

@Override

@Transactional(rollbackFor = Exception.class)

public void testCommonFieldAutoUpdate(UpdateUserRequest updateReq) {

UserDO existedEntity = getById(updateReq.getId());

if (existedEntity == null) {

throw new BizEx("数据不存在");

}

existedEntity.setAddress(updateReq.getAddress());

existedEntity.setGender(updateReq.getGender());

updateById(existedEntity);

}

}

你这个问题非常经典,也具有一定的代表性 ------ 在 MetaObjectHandler 中设置的时间是正确的(东八区时间),但在 Service 层通过 updateById() 更新时,数据库中记录的 modifiedTime 却少了 8 小时。

我们来一步步分析问题的本质和根本原因。

✅ 现象总结

场景 时间值 是否正确

MetaObjectHandler.updateFill() 中生成时间 Date.from(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toInstant()) ✅ 正确

数据库中的 modified_time 字段 存入的是 UTC 时间(比北京时间晚 8 小时) ❌ 错误

Java 实体类字段类型 Date ⚠️ 潜在问题

🧩 核心问题:java.util.Date 是一个"无时区"的包装器,但内部保存的是 UTC 时间戳

关键点:

new Date() 或 Date.from(...) 创建的对象,其本质是一个从 1970-01-01T00:00:00Z 开始的毫秒数(即 Unix 时间戳),它本身不包含任何时区信息。

当你在代码中打印 Date.toString() 时,它会根据 JVM 的默认时区格式化输出,所以看起来像是本地时间。

但是当这个 Date 被写入数据库时,JDBC 驱动、MyBatis、数据库连接池等组件可能会以不同的方式处理这个时间值,导致出现时区偏差。

🔍 为什么 MetaObjectHandler 中设置的是正确时间?

你的这段代码:

Java

深色版本

this.strictUpdateFill(metaObject, "modifiedTime", () ->

Date.from(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toInstant()), Date.class);

实际上是这样做的:

获取了当前东八区时间(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")))

转换为 Instant(这是标准的 UTC 时间)

再转换为 Date(仍然是基于 UTC 时间戳)

此时,Date 对象表示的是一个 UTC 时间点。但由于你在日志或调试中看到的是类似 Tue Jun 04 15:30:00 CST 2025 这样的字符串,这其实是 Date.toString() 使用了 JVM 默认时区(可能是 Asia/Shanghai)进行展示,因此看起来是正确的。

💥 为什么 Service 层更新的时候却差了 8 小时?

当你执行如下代码时:

Java

深色版本

UserDO existedEntity = getById(updateReq.getId());

existedEntity.setAddress(updateReq.getAddress());

updateById(existedEntity);

此时:

MyBatis Plus 会将 existedEntity 中的 modifiedTime 字段(类型是 Date)直接传给 JDBC 驱动;

如果数据库字段是 DATETIME 类型,则 JDBC 驱动通常会将 Date 直接当作 UTC 时间 来处理(特别是 MySQL 8+ 的行为);

所以即使你存入的是 Date.from(ZonedDateTime.now(...).toInstant()),数据库最终可能认为这是一个 UTC 时间,而不是东八区时间;

最终显示出来就少了 8 小时。

✅ 解决方案

✅ 推荐做法一:统一使用 LocalDateTime + TypeHandler(推荐)

  1. 修改实体类字段为 LocalDateTime

Java

深色版本

private LocalDateTime modifiedTime;

  1. 在自动填充中使用 LocalDateTime::now

Java

深色版本

@Override

public void updateFill(MetaObject metaObject) {

this.strictUpdateFill(metaObject, "modifiedTime", LocalDateTime::now, LocalDateTime.class);

}

  1. 添加 TypeHandler 支持

确保你的项目中引入了以下依赖(用于支持 LocalDateTime 到数据库的映射):

Xml

深色版本

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-typehandlers-jsr310</artifactId>

<version>3.5.3</version> <!-- 请根据你的 MP 版本调整 -->

</dependency>

MyBatis Plus 会自动识别并注册这些处理器。

  1. 数据库字段类型应为 DATETIME 或 TIMESTAMP

MySQL 推荐使用 TIMESTAMP,它会自动存储为 UTC 并在查询时根据连接参数转为本地时间。

✅ 替代做法二:继续使用 Date,但修改 JDBC 连接配置指定时区

如果你坚持使用 Date 类型,可以在数据库连接字符串中显式指定时区。

示例(MySQL):

Properties

深色版本

jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false

serverTimezone=GMT%2B8 表示服务器时间偏移量为 UTC+8,这样 JDBC 驱动就知道如何正确地把 Date 映射到数据库时间。

📌 总结对比

方案 类型 优点 缺点

✅ 使用 LocalDateTime + TypeHandler 推荐 语义清晰,避免时区混乱,Java 8 原生支持 需要引入额外依赖

✅ 使用 Date + JDBC 设置时区 可行 不改动现有代码结构 容易遗漏或配置错误

❌ 继续用 Date 且不改配置 ❌ 无需改代码 极容易出现时区偏差

✅ 建议操作顺序

把 modifiedTime 字段改为 LocalDateTime 类型;

修改自动填充逻辑为 LocalDateTime::now;

引入 mybatis-plus-typehandlers-jsr310;

数据库字段保持为 DATETIME 或改为 TIMESTAMP;

(可选)数据库连接串加上 serverTimezone=GMT%2B8。

🧪 如何验证是否修复成功?

在 Debug 模式下查看 modifiedTime 字段是否是预期时间;

查看数据库记录是否与代码中一致;

日志中打印出 modifiedTime,确认输出格式和时区;

多台不同系统时区的机器测试,确认一致性。

如你能提供:

数据库字段定义(DATETIME / TIMESTAMP)

使用的数据库类型及版本(MySQL 8?PostgreSQL?)

MyBatis Plus 版本

数据库连接 URL 和驱动版本

我可以进一步帮你做更精准的建议。

相关推荐
Jelian_6 分钟前
SpringBoot自定义实体类字段的校验注解
java·spring boot·spring
菥菥爱嘻嘻2 小时前
JS手写代码篇---Pomise.race
开发语言·前端·javascript
南瓜胖胖2 小时前
【R语言编程绘图-调色】
开发语言·r语言
lanbing3 小时前
非常适合初学者的Golang教程
开发语言·后端·golang
stormsha4 小时前
GO语言进阶:掌握进程OS操作与高效编码数据转换
开发语言·数据库·后端·golang·go语言·源代码管理
老神在在0015 小时前
javaEE1
java·开发语言·学习·java-ee
魔道不误砍柴功5 小时前
《接口和抽象类到底怎么选?设计原则与经典误区解析》
java·开发语言
small_white_robot6 小时前
Tomcat- AJP协议文件读取/命令执行漏洞(幽灵猫复现)详细步骤
java·linux·网络·安全·web安全·网络安全·tomcat
我是李武涯6 小时前
C++ 条件变量虚假唤醒问题的解决
开发语言·c++·算法
图梓灵6 小时前
Maven与Spring核心技术解析:构建管理、依赖注入与应用实践
java·笔记·spring·maven