1. 概述

在Java应用中持久化时间戳是常见需求,通常推荐使用UTC时间戳以避免时区复杂性。但某些场景下,我们可能需要存储带时区偏移的时间戳。

本文将探讨**如何同时持久化OffsetDateTimeZoneOffset**,并对比两种实现方式的差异。

2. 持久化OffsetDateTime

JPA原生支持OffsetDateTime类型。**OffsetDateTime表示带UTC偏移量的日期时间**。

定义一个存储时间戳的实体类:

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "inserted_at_timestamp")
    private OffsetDateTime insertedAt;

    // 标准getter/setter
}

通过测试验证持久化行为:

@Test
void whenPersistingOffsetDateTime_thenIsPersisted() {
    entityManager.getTransaction().begin();
    Person person1 = new Person();
    person1.setInsertedAt(OffsetDateTime.of(2024,10,31,12,0,0,0, ZoneOffset.ofHours(5)));
    entityManager.persist(person1);

    Person savedEntity = entityManager.createQuery("from Person", Person.class)
      .getSingleResult();
    assertNotNull(savedEntity);
    entityManager.getTransaction().commit();
}

查看Hibernate日志输出:

Hibernate: 
    /* insert for
        com.baeldung.hibernate.timezonecolumn.model.Person */insert 
    into
        Person (inserted_at_timestamp, id) 
    values
        (?, ?)
#1731106113065 | took 0ms | statement | connection 0| url jdbc:p6spy:h2:mem:test
/* insert for com.baeldung.hibernate.timezonecolumn.model.Person */insert into Person (inserted_at_timestamp,id) values (?,?)
/* insert for com.baeldung.hibernate.timezonecolumn.model.Person */insert into Person (inserted_at_timestamp,id) values ('2024-10-31T12:00+05:00',1);

Hibernate将OffsetDateTime及其偏移量保存在单列中。⚠️ 注意:JPA使用TIMESTAMP(n) WITH TIME ZONE(或等效)SQL类型,但并非所有数据库都支持此特性。

3. 使用@TimeZoneColumn持久化OffsetDateTime

当需要单独存储ZoneOffset或数据库不支持OffsetDateTime时,可使用@TimeZoneColumn注解将偏移量存入独立列

修改实体类,添加新字段并使用注解:

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "inserted_at_timestamp")
    private OffsetDateTime insertedAt;

    @Column(name = "updated_at_timestamp")
    @TimeZoneStorage(TimeZoneStorageType.COLUMN)
    @TimeZoneColumn(name="last_updated_offset")
    private OffsetDateTime lastUpdatedAt;

    // 标准getter/setter
}

JPA将按指定方式存储偏移量。执行测试验证:

@Test
void whenPersistingOffsetDateTimeWithTimeZoneColumn_thenIsPersisted() {
    entityManager.getTransaction().begin();
    Person person1 = new Person();
    person1.setLastUpdatedAt(OffsetDateTime.of(2024,10,31,12,0,0,0, ZoneOffset.ofHours(5)));
    entityManager.persist(person1);
    Person savedEntity = entityManager.createQuery("from Person", Person.class)
      .getSingleResult();
    assertNotNull(savedEntity);
    entityManager.getTransaction().commit();
}

检查Hibernate日志确认存储方式:

Hibernate: 
    /* insert for
        com.baeldung.hibernate.timezonecolumn.model.Person */insert 
    into
        Person (inserted_at_timestamp, updated_at_timestamp, last_updated_offset, id) 
    values
        (?, ?, ?, ?)
#1731108407719 | took 1ms | statement | connection 0| url jdbc:p6spy:h2:mem:test
/* insert for com.baeldung.hibernate.timezonecolumn.model.Person */insert into Person (inserted_at_timestamp,updated_at_timestamp,last_updated_offset,id) values (?,?,?,?)
/* insert for com.baeldung.hibernate.timezonecolumn.model.Person */insert into Person (inserted_at_timestamp,updated_at_timestamp,last_updated_offset,id) values ('2024-10-30T12:00+05:00','2024-10-31T07:00:00Z',18000,1);

ZoneOffset被存储在独立列中。这种分离存储的优势在于: ✅ 更灵活的查询能力
✅ 简化数据维护
✅ 兼容不支持时区类型的数据库

4. 总结

本文对比了OffsetDateTime的两种持久化方案:

方案 存储方式 适用场景
默认方式 单列存储时间+偏移量 数据库支持TIMESTAMP WITH TIME ZONE
@TimeZoneColumn 双列分离存储 需要独立操作偏移量或数据库不支持时区类型

使用@TimeZoneColumn能显著提升代码可维护性,同时确保对不同数据库的兼容性。实际开发中,建议根据数据库特性和业务需求选择合适方案。

完整源码见GitHub仓库


原始标题:Mapping OffsetDateTime ZoneOffset With Hibernate TimeZoneColumn | Baeldung