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"
}
相关推荐
leobertlan6 小时前
2025年终总结
前端·后端·程序员
面向Google编程7 小时前
从零学习Kafka:数据存储
后端·kafka
易安说AI8 小时前
Claude Opus 4.6 凌晨发布,我体验了一整晚,说说真实感受。
后端
易安说AI8 小时前
Ralph Loop 让Claude无止尽干活的牛马...
前端·后端
易安说AI8 小时前
用 Claude Code 远程分析生产日志,追踪 Claude Max 账户被封原因
后端
JH30738 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
颜酱9 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
qq_124987075311 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_11 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_8187320611 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea