SpringBoot数据存储时区选择,符合国际化和特定时区方案

摘要:本文主要介绍在SpringBoot项目中数据时区存储的讨论,并介绍在常用的数据库中,应该选择什么样的类型来存储时间;下面的案例采用SpringBoot + MybatisPlus

背景

项目适应国际化,应该采用何种方式让不同时区的人看到各自时区的时间转换数据。

Java 常用的时间类型对比

特性 Date LocalDateTime ZonedDateTime
时区信息 隐含 UTC(实际设计模糊) 无时区 明确包含时区
可变性 可变(线程不安全) 不可变(线程安全) 不可变(线程安全)
设计合理性 旧 API,存在缺陷 新 API,推荐使用 新 API,推荐使用
适用场景 兼容旧代码 本地时间(无时区依赖) 跨时区时间

推荐使用LocalDateTime

存储的时间不带时区,更推荐的是是使用UTC标准时区,业务方根据当前时区转换。 如果确定只有中国,那么就存储+8的中国时间,就不用UTC了。

  • 创建UTC时间:LocalDateTime.now(ZoneOffset.UTC)
  • 如果固定是+8的中国时区:ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toLocalDateTime()

如果代码中使用的是ZoneDateTime

那么你需要在存储数据库的时候使用LocalDateTime,其他地方使用ZoneDateTime

各种数据库时间类型推荐选择

数据库 不带时区的时间类型 说明
MySQL DATETIME 范围大,无时区,避免 TIMESTAMP
PostgreSQL TIMESTAMP WITHOUT TIME ZONE 明确无时区,支持高精度
Oracle DATE(基础)或 TIMESTAMP(高精度) 均不带时区,TIMESTAMP 精度更高
SQL Server DATETIME2(优先)或 DATETIME DATETIME2 精度更高,无时区
Kingbase TIMESTAMP WITHOUT TIME ZONE 兼容 PostgreSQL,无时区

实际使用案例

SpringBoot+Mybatis-Plus案例

数据库表Mysql

sql 复制代码
CREATE TABLE `page` (
  `id` bigint(20) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

PageEntity

less 复制代码
@Data
@TableName("page")
public class PageEntity {

    @TableId
    private Long id;

    private String name;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;

}

PageMapper

java 复制代码
@Mapper
public interface PageMapper extends BaseMapper<PageEntity> {
}

PageUtcRes + PageUtc8Res

  • PageUtcRes
kotlin 复制代码
@Data
public class PageUtcRes {

    private Long id;

    private String name;

    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
    private LocalDateTime createTime;

    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
    private LocalDateTime updateTime;
}
  • PageUtc8Res
kotlin 复制代码
@Data
public class PageUtc8Res {

    private Long id;

    private String name;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
}

PageController

scss 复制代码
@RestController
@RequestMapping("page")
public class PageController {

    @Autowired
    private PageMapper pageMapper;

    @GetMapping("add")
    public PageUtcRes add(){
        PageEntity pageEntity = new PageEntity();
        pageEntity.setId(IdWorker.getId());
        pageEntity.setName(UUID.randomUUID().toString());
        pageEntity.setCreateTime(LocalDateTime.now(ZoneOffset.UTC));
        pageEntity.setUpdateTime(LocalDateTime.now(ZoneOffset.UTC));
        pageMapper.insert(pageEntity);
        PageUtcRes pageUtcRes = new PageUtcRes();
        BeanUtils.copyProperties(pageEntity, pageUtcRes);
        return pageUtcRes;
    }

    @GetMapping(value = "get")
    public PageUtcRes get(Long id) {
        PageEntity pageEntity = pageMapper.selectById(id);
        PageUtcRes pageUtcRes = new PageUtcRes();
        BeanUtils.copyProperties(pageEntity, pageUtcRes);
        return pageUtcRes;
    }

    @GetMapping("addUtc8")
    public PageUtc8Res addUtc8(){
        PageEntity pageEntity = new PageEntity();
        pageEntity.setId(IdWorker.getId());
        pageEntity.setName(UUID.randomUUID().toString());
        pageEntity.setCreateTime(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toLocalDateTime());
        pageEntity.setUpdateTime(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toLocalDateTime());
        pageMapper.insert(pageEntity);
        PageUtc8Res pageUtc8Res = new PageUtc8Res();
        BeanUtils.copyProperties(pageEntity, pageUtc8Res);
        return pageUtc8Res;
    }

    @GetMapping(value = "getUtc8")
    public PageUtc8Res getUtc8(Long id) {
        PageEntity pageEntity = pageMapper.selectById(id);
        PageUtc8Res pageUtc8Res = new PageUtc8Res();
        BeanUtils.copyProperties(pageEntity, pageUtc8Res);
        return pageUtc8Res;
    }


}

结果

只有中国用默认utc+8
  • http://localhost:8080/page/addUtc8
json 复制代码
{
    "id": 1946371991577559042,
    "name": "9b29f0fc-36aa-4b5f-88b4-6401d5323184",
    "createTime": "2025-07-19 08:50:19",
    "updateTime": "2025-07-19 08:50:19"
}
  • http://localhost:8080/page/getUtc8?id=1946371991577559042
json 复制代码
{
    "id": 1946371991577559042,
    "name": "9b29f0fc-36aa-4b5f-88b4-6401d5323184",
    "createTime": "2025-07-19 08:50:19",
    "updateTime": "2025-07-19 08:50:19"
}
国际化utc时间
  • http://localhost:8080/page/add
json 复制代码
{
    "id": 1946377758359744513,
    "name": "b9fc00d8-ebc5-4126-945b-9268f12efc93",
    "createTime": "2025-07-19T01:13:14Z",
    "updateTime": "2025-07-19T01:13:14Z"
}
  • http://localhost:8080/page/get?id=1946377758359744513
json 复制代码
{
"id": 1946377758359744513,
"name": "b9fc00d8-ebc5-4126-945b-9268f12efc93",
"createTime": "2025-07-19T01:13:14Z",
"updateTime": "2025-07-19T01:13:14Z"
}
相关推荐
江畔柳前堤3 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
软萌萌的14 小时前
Java Spring Boot 修改yml配置&加载顺序规则
java·spring boot·python
陈随易5 小时前
moon,apt和yum之外linux系统命令安装新选择
前端·后端·程序员
码事漫谈6 小时前
当单库日增 4.5TB,异构实时同步还能稳得住吗?
后端
IT_陈寒8 小时前
SpringBoot自动配置的坑,这次真踩疼我了
前端·人工智能·后端
shengjk18 小时前
AI Engineering 五代演进史:Prompt、RAG、Agent 到 Graph 的架构革命
后端
子兮曰8 小时前
AI 浏览器 Agent 的安全困局:当自动化工具可以偷走你的登录态
前端·人工智能·后端
妙码生花8 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(四十八):菜单规则管理实现
前端·后端·ai编程
XuCoder8 小时前
Redis 持久化:RDB 与 AOF
后端
Conan在掘金8 小时前
鸿蒙 7.0 智能体框架 2.0:意图即服务根因——从 InsightIntentExecutor 到 HMAF 2.0 Skill 演进
后端