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"
}
相关推荐
IT_陈寒12 小时前
React Hooks闭包陷阱:你以为的state可能早就过期了
前端·人工智能·后端
小码哥_常12 小时前
细说API:颠覆认知!重新认识RESTful的真正精髓
后端
用户990450177800912 小时前
基于flowable实现在线表单+工作流
后端
苏三说技术12 小时前
Artha已接入MCP,线上问题能用AI排查了!
后端
用户9623779544812 小时前
代码审计 | CC2 链 —— _tfactory 赋值问题 PriorityQueue 新入口
后端
爱敲代码的小鱼14 小时前
springboot(2)从基础到项目创建:
java·spring boot·spring
Vfw3VsDKo15 小时前
Maui 实践:Go 接口以类型之名,给 runtime 传递方法参数
开发语言·后端·golang
i220818 Faiz Ul16 小时前
动漫商城|基于springboot + vue动漫商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·动漫商城系统
是真的小外套16 小时前
第十五章:XXE漏洞攻防与其他漏洞全解析
后端·计算机网络·php
ybwycx18 小时前
SpringBoot下获取resources目录下文件的常用方法
java·spring boot·后端