JPA使用ZoneOffset存储OffsetDateTime [英] JPA Storing OffsetDateTime with ZoneOffset

查看:279
本文介绍了JPA使用ZoneOffset存储OffsetDateTime的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下实体类:

  @Entity 
public class Event {
private OffsetDateTime的startDateTime;
// ...
}

然而,坚持和那么使用JPA 2.2从数据库读取实体会导致信息丢失 startDateTime 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 界面来完成这项工作。



请参阅:



小心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-QL

  • Need to be able to retrieve the same ZoneOffset as of the OffsetDateTime 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:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆