摘要:本文主要介绍在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
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"
}