我有以下实体类:
@Entity public class Event { private OffsetDateTime startDateTime; // ... }
然而, 持续的,然后从与JPA 2.2结果数据库读取的实体/在信息丢失 :所述ZoneOffset的startDateTime改变UTC(在ZoneOffset所使用的数据库的时间戳)。例如:
ZoneOffset
startDateTime
UTC
Event e = new Event(); e.setStartDateTime(OffsetDateTime.parse("2018-01-02T09:00-05:00")); e.getStartDateTime().getHour(); // 9 e.getStartDateTime().getOffset(); // ZoneOffset.of("-05:00") // ... entityManager.persist(e); // Stored startDateTime as timestamp 2018-01-02T14:00Z Event other = entityManager.find(Event.class, e.getId()); other.getStartDateTime().getHour(); // 14 - DIFFERENT other.getStartDateTime().getOffset(); // ZoneOffset.of("+00:00") - DIFFERENT
我需要使用OffsetDateTime:我不能使用ZonedDateTime,因为区域规则会发生变化(无论如何,这也会遭受此信息丢失的困扰)。我无法使用LocalDateTime,因为某Event事发生在世界任何地方,ZoneOffset出于准确性原因,我需要它发生时的原始内容。我无法使用,Instant因为用户填写了事件的开始时间(事件就像约会一样)。
OffsetDateTime
ZonedDateTime
LocalDateTime
Event
Instant
要求:
需要能够对>, <, >=, <=, ==, !=JPA-QL中的时间戳执行比较
>, <, >=, <=, ==, !=
需要能够检索与持久化的相同ZoneOffset的内容OffsetDateTime
//编辑:我更新了答案以反映JPA 2.1版和2.2版之间的差异。
//编辑2:添加了JPA 2.2规范链接
JPA v2.1不知道Java 8类型,并将尝试对提供的值进行字符串化。对于LocalDateTime,Instant和OffsetDateTime,它将使用toString()方法并将相应的字符串保存到目标字段。
也就是说,您必须告诉JPA如何将您的值转换为相应的数据库类型,例如java.sql.Date或java.sql.Timestamp。
java.sql.Date
java.sql.Timestamp
实施并注册AttributeConverter界面以使其工作。
AttributeConverter
看到:
当心亚当·比恩(Adam Bien)的错误实现:必须首先对LocalDate进行分区。
只是不要创建属性转换器。它们已经包括在内。
//更新2:
您可以在此处的规范中看到这一点:JPA 2.2 spec。滚动到最后一页以查看时间类型。
如果使用jpql表达式,请确保使用Instant对象,并在PDO类中也使用Instant。
例如
// query does not make any sense, probably. query.setParameter("createdOnBefore", Instant.now());
这样很好。
java.time.Instant
无论如何,即使您具有ZonedDateTime或OffsetDateTime,从数据库读取的结果也将始终为UTC,因为数据库存储的是即时时间,而与时区无关。 时区实际上只是显示信息(元数据) 。
因此,我建议改为使用Instant它,并且仅在需要时将其转换为“区域”或“偏移时间”类。要恢复给定区域或偏移量的时间,请将区域或偏移量分别存储在其自己的数据库字段中。
JPQL比较将与此解决方案一起使用,只需始终保持即时操作即可。
PS:我最近与一些Spring员工进行了交谈,他们还同意,除了Instant之外,您再也不会坚持任何其他方法。只有一个瞬间是特定的时间点,然后可以使用元数据进行转换。
根据JPA 2.2规范,未提及CompositeValues。这意味着,他们没有将其纳入规范中,并且此时您不能将单个字段持久保存到多个数据库列中。搜索“复合”,仅看到与ID相关的提及。
但是,正如此答案的注释中所述,Hibernate可能能够做到这一点。
我创建此示例时牢记以下原则:为扩展而打开,为修改而关闭。在此处阅读有关此原则的更多信息:Wikipedia上的开放/封闭原则。
这意味着,您可以将当前字段保留在数据库中(时间戳记),并且只需要添加其他列即可,这不会有任何问题。
另外,您的实体可以保留OffsetDateTime的设置方法和获取方法。内部结构应该与调用者无关。这意味着,此建议完全不会损害您的api。
一个实现可能看起来像这样:
@Entity public class UserPdo { @Column(name = "created_on") private Instant createdOn; @Column(name = "display_offset") private int offset; public void setCreatedOn(final Instant newInstant) { this.createdOn = newInstant; this.offset = 0; } public void setCreatedOn(final OffsetDateTime dt) { this.createdOn = dt.toInstant(); this.offset = dt.getOffset().getTotalSeconds(); } // derived display value public OffsetDateTime getCreatedOnOnOffset() { ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(this.offset); return this.createdOn.atOffset(zoneOffset); } }