下面的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;
2. 在自動填充中使用 LocalDateTime::now
Java
深色版本
@Override
public void updateFill(MetaObject metaObject) {
? ? this.strictUpdateFill(metaObject, "modifiedTime", LocalDateTime::now, LocalDateTime.class);
}
3. 添加 TypeHandler 支持
確保你的項目中引入了以下依賴(用于支持 LocalDateTime 到數據庫的映射):
Xml
深色版本
<dependency>
? ? <groupId>com.baomidou</groupId>
? ? <artifactId>mybatis-plus-typehandlers-jsr310</artifactId>
? ? <version>3.5.3</version> <!-- 請根據你的 MP 版本調整 -->
</dependency>
MyBatis Plus 會自動識別并注冊這些處理器。
4. 數據庫字段類型應為 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 和驅動版本
我可以進一步幫你做更精準的建議。