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

112 阅读4分钟

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

背景

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

Java 常用的时间类型对比

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

推荐使用LocalDateTime

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

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

如果代码中使用的是ZoneDateTime

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

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

数据库不带时区的时间类型说明
MySQLDATETIME范围大,无时区,避免 TIMESTAMP
PostgreSQLTIMESTAMP WITHOUT TIME ZONE明确无时区,支持高精度
OracleDATE(基础)或 TIMESTAMP(高精度)均不带时区,TIMESTAMP 精度更高
SQL ServerDATETIME2(优先)或 DATETIMEDATETIME2 精度更高,无时区
KingbaseTIMESTAMP WITHOUT TIME ZONE兼容 PostgreSQL,无时区

实际使用案例

SpringBoot+Mybatis-Plus案例

数据库表Mysql

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

@Data
@TableName("page")
public class PageEntity {

    @TableId
    private Long id;

    private String name;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;

}

PageMapper

@Mapper
public interface PageMapper extends BaseMapper<PageEntity> {
}

PageUtcRes + PageUtc8Res

  • PageUtcRes
@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

@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

@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
{
    "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
{
    "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
{
    "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
{
"id": 1946377758359744513,
"name": "b9fc00d8-ebc5-4126-945b-9268f12efc93",
"createTime": "2025-07-19T01:13:14Z",
"updateTime": "2025-07-19T01:13:14Z"
}