JPA使用ZoneOffset存储OffsetDateTime [英] JPA Storing OffsetDateTime with ZoneOffset
问题描述
我有以下实体类:
@Entity
public class Event {
private OffsetDateTime的startDateTime;
// ...
}
然而,坚持和那么使用JPA 2.2从数据库读取实体会导致信息丢失: startDateTime $ c的
ZoneOffset
$ c>更改为 UTC
(数据库时间戳使用的 ZoneOffset
)。例如:
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); //将startDateTime存储为时间戳2018-01-02T14:00Z
Event other = entityManager.find(Event.class,e.getId());
other.getStartDateTime()。getHour(); // 14 - 不同
other.getStartDateTime()。getOffset(); // ZoneOffset.of(+ 00:00) - 不同
我需要使用 OffsetDateTime
:我不能使用 ZonedDateTime
,因为区域规则发生了变化(无论如何也会有这种信息丢失)。我不能使用 LocalDateTime
,因为 Event
发生在世界任何地方,我需要原始的 ZoneOffset
出于准确性的原因。我不能使用 Instant ,因为用户填写了事件的开始时间(事件就像约会)。
要求:
-
需要能够执行
> ;,<> =,<=,==,!=
在JPA-QL中的时间戳比较 -
需要能够检索与持有
OffsetDateTime 相同的 ZoneOffset
//编辑:我更新了答案以反映JPA版本2.1与2.2之间的差异。
//编辑2:添加JPA 2.2规范链接
JPA 2.1的问题
JPA v2.1不知道java 8类型,并会尝试对提供的值进行字符串化。对于LocalDateTime,Instant和OffsetDateTime,它将使用toString()方法并将相应的字符串保存到目标字段中。
这就是说,您必须告诉JPA如何将您的值添加到相应的数据库类型,例如 java.sql.Date
或 java.sql.Timestamp
。
执行并注册 AttributeConverter
界面来完成这项工作。
请参阅:
- https://stuetzpunkt.wordpress.com/2015/03/15/persisting-localdate-with-jpa-2-1/
- http://www.adam-bien。 com / roller / abien / entry / new_java_8_date_and
小心Adam Bien的错误实施:LocalDate需要分区首先。
使用JPA 2.2
只是不要创建属性转换器。它们已包含在内。
//更新2: 您可以看到这在规格这里: JPA 2.2规范。滚动到最后一页,查看时间类型是否包含在内。
如果您使用jpql表达式,请务必使用Instant对象并在您的PDO类。
eg
//查询不会生成任何感觉,可能。
query.setParameter(createdOnBefore,Instant.now());
这很好。
使用 java.time.Instant
代替其他格式
无论如何,即使您有ZonedDateTime或OffsetDateTime,结果从数据库读取将始终是UTC,因为数据库存储即时,而不考虑时区。 时区实际上只是显示信息(元数据)。
因此,我建议使用即时
代替,并将其转换为Zoned或Offset Time类别,仅在需要时进行分类。要恢复给定区域或偏移量的时间,请将区域或偏移量分别存储在其自己的数据库字段中。
JPQL比较可以使用此解决方案,只需保留PS:我最近和一些春天的人谈过话了,他们也同意你永远不会坚持任何东西而不是瞬间。只有瞬间是特定的时间点,然后可以使用元数据进行转换。
使用复合值
<根据规范 JPA 2.2规范,CompositeValues没有提及。这意味着,他们没有将其纳入规范中,而且此时不能将单个字段保存到多个数据库列中。搜索Composite,只查看与ID有关的提示。
Howerever,Hibernate也许能够做到这一点,正如此答案的评论中所述。 >
示例实现
我以这个原则为例创建了这个示例:Open for extension,closed for modification。在此处阅读有关此原则的详细信息:维基百科上的开放/关闭原则。
这意味着,您可以将当前字段保留在数据库中(时间戳记),并且您只需添加一个额外的列,这不应该受到伤害。
另外,您的实体可以保留OffsetDateTime的设置者和获取者。内部结构不应该成为呼叫者的担忧。这意味着,这个提议不应该伤害你的API。
一个实现可能如下所示:
@Entity
public class UserPdo {
@Column(name =created_on)
private即时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();
}
//派生显示值
public OffsetDateTime getCreatedOnOnOffset(){
ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(this.offset);
返回this.createdOn.atOffset(zoneOffset);
}
}
I have the following entity class:
@Entity
public class Event {
private OffsetDateTime startDateTime;
// ...
}
However, persisting and then reading the entity to/from the database with JPA 2.2 results in a loss of information: the ZoneOffset
of startDateTime
changes to UTC
(the ZoneOffset
used by the database timestamp). For instance:
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
I need to use OffsetDateTime
: I cannot use ZonedDateTime
, because zone rules change (and that also suffers from this information loss anyway). I cannot use LocalDateTime
, since an Event
happens anywhere in the world and I need the original ZoneOffset
from when it happened for accuracy reasons. I can not use Instant
because a user fills in the starting time of an event (the event is like an appointment).
Requirements:
Need to be able to perform
>, <, >=, <=, ==, !=
comparisons on timestamps in JPA-QLNeed to be able to retrieve the same
ZoneOffset
as of theOffsetDateTime
that was persisted
// Edit: I updated the answer to reflect differences between JPA version 2.1 and 2.2.
// Edit 2: Added JPA 2.2 spec link
The Problem with JPA 2.1
JPA v2.1 does not know of java 8 types and will try to stringify the provided value. For LocalDateTime, Instant and OffsetDateTime it will use the toString() method and save the corresponding string to the target field.
This said, you must tell JPA how to convert your value to a corresponding database type, like java.sql.Date
or java.sql.Timestamp
.
Implement and register the AttributeConverter
interface to make this work.
See:
- https://stuetzpunkt.wordpress.com/2015/03/15/persisting-localdate-with-jpa-2-1/
- http://www.adam-bien.com/roller/abien/entry/new_java_8_date_and
Beware of the errornous implementation of Adam Bien: LocalDate needs to be Zoned first.
Using JPA 2.2
Just do not create the attribute converters. They are already included.
// Update 2:
You can see this in the specs here: JPA 2.2 spec. Scroll to the very last page to see, that the time types are included.
If you use jpql expressions, be sure to use the Instant object and also use Instant in your PDO classes.
e.g.
// query does not make any sense, probably.
query.setParameter("createdOnBefore", Instant.now());
This works just fine.
Using java.time.Instant
instead of other formats
Anyway, even if you have a ZonedDateTime or OffsetDateTime, the result read from the database will always be UTC, because the database stores an instant in time, regardless of the timezone. The timezone is actually just display information (meta data).
Therefore, I recommend to use Instant
instead, and coonvert it to Zoned or Offset Time classses only when needed. To restore the time at the given zone or offset, store either the zone or the offset separately in its own database field.
JPQL comparisons will work with this solution, just keep working with instants all the time.
PS: I recently talked to some Spring guys, and they also agreed that you never persist anything else than an Instant. Only an instant is a specific point in time, which then can be converted using metadata.
Using a composite value
According to the spec JPA 2.2 spec, CompositeValues are not mentioned. This means, they did not make it into the specification, and you cannot persist a single field into multiple database columns at this time. Search for "Composite" and see only mentions related to IDs.
Howerever, Hibernate might be capable of doing this, as mentioned in this comments of this answer.
Example implementation
I created this example with this principle in mind: Open for extension, closed for modification. Read more about this principle here: Open/Closed Principle on Wikipedia.
This means, you can keep your current fields in the database (timestamp) and you only need to add an additional column, which should not hurt.
Also, your entity can keep the setters and getters of OffsetDateTime. The internal structure should be no concern of callers. This means, this proposal should not hurt your api at all.
An implementation might look like this:
@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);
}
}
这篇关于JPA使用ZoneOffset存储OffsetDateTime的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!