JPA支持Java 8新的日期和时间API

58

我正在使用Java 8开发我的新项目。

我试图在Java 8中使用新的日期和时间API,但是我不知道JPA 2.1是否完全支持这个新的日期和时间API。

请分享您在JPA对Java 8新日期和时间API支持方面的经验/观点。

我能否安全地在Java 8中使用新的日期和时间API与JPA 2.1一起工作?

更新:

我正在使用Hibernate(4.3.5.Final)作为JPA实现。


看看这个:http://javajeedevelopment.blogspot.fr/2016/11/datetime-jpahibernate-mapping.html - ben rhouma moez
这是不使用Java引用类型处理数据库层的时间数据的主要原因。如果日期时间的数据库格式是数字格式,例如修改儒略日数(可以在双精度中保持毫秒级别的日期时间),则ORM支持永远不会出现问题。此外,JPA 2.1中的转换器工具使得在所选时间库的对象和数字格式之间进行互相转换变得非常容易。 - scottb
8个回答

73

对于 Hibernate 5.X,只需添加:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-java8</artifactId>
        <version>${hibernate.version}</version>
    </dependency>

@NotNull
@Column(name = "date_time", nullable = false)
protected LocalDateTime dateTime;

将不需要任何额外的努力即可工作。 请参见https://hibernate.atlassian.net/browse/HHH-8844

更新:

请查看Jeff Morin的评论:自Hibernate 5.2.x起,这已经足够了。

 <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-core</artifactId>
     <version>5.2.1.Final</version>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-...</artifactId>
     <version>4.3.1.RELEASE</version>
 </dependency>

请查看https://github.com/hibernate/hibernate-orm/wiki/Migration-Guide---5.2Integrate Hibernate 5.2 with Spring framework 4.x


8
从Hibernate 5.2.x开始,这种补充已经不再必要了,因为这些Java 8特性已经合并到Hibernate核心中。 - Jeff Morin
1
谢谢!同时迁移到Spring 4.3.x(https://dev59.com/R-k6XIcBkEYKwwoYBvbx)也可以工作! - Grigory Kislin
虽然我不明白,但如果Hibernate不支持新的日期API会发生什么?现在对于LocalDateTime列,它们以blob类型存储在MySQL中,您认为这是正确的行为吗? - Tiina

21

JPA 2.1是在Java 1.8之前发布的规范,因此不强制要求支持它。显然,一些实现可能会支持一些Java 1.8功能。有些实现可能存在Java 1.8字节码问题(例如EclipseLink)。我知道DataNucleus支持java.time和Java 1.8,因为那是我使用的。您需要检查您的实现以确定其支持级别。

已经要求JPA 2.2支持java.time类型,请参见此问题 https://java.net/jira/browse/JPA_SPEC-63


1
任何人都可以搜索Hibernate支持的相关内容。您可以在此找到https://hibernate.atlassian.net/browse/HHH-8844,因此,您选择的JPA实现不支持它。 - Neil Stockton
EclipseLink现在已经不再存在与Java 8字节码相关的任何问题。 - eskatos
@eskatos,EclipseLink的修复版本号是多少?我没有看到自2013年9月26日(v2.5.1)以来的任何发布版本,并且在Maven Central中也没有。 - Neil Stockton
2
哦,我的错,如果实体类中存在lambda表达式,EclipseLink 2.5.1将失败。但是对我来说不是这种情况,这就是为什么一切都按预期工作的原因。请参见https://bugs.eclipse.org/bugs/show_bug.cgi?id=429992 - eskatos
2
链接已损坏。 - thatsIch
显示剩余2条评论

12

JPA 2.2支持java.time

JPA 2.2现在支持LocalDateLocalTimeLocalDateTimeOffsetTimeOffsetDateTime

<dependency>
  <groupId>javax.persistence</groupId>
  <artifactId>javax.persistence-api</artifactId>
  <version>2.2</version>
</dependency>

对于JPA 2.2实现,可以使用Hibernate 5.2EclipseLink 2.7

Hibernate 5支持比JPA 2.2更多的Java类型,如DurationInstantZonedDateTime

更多信息:


9

org.jadira.usertype可以用于持久化JSR 310日期和时间API。

查看此示例项目

来自示例项目,

@MappedSuperclass
public class AbstractEntity {

    @Id @GeneratedValue Long id;

    @CreatedDate//
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")//
    ZonedDateTime createdDate;

    @LastModifiedDate//
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")//
    ZonedDateTime modifiedDate;
}

我不确定,但我认为它也应该适用于Eclipselink。试一下吧。 - TheKojuEffect
1
示例项目的链接现在使用了一些Spring特定的注解。这是同一项目旧版本的提交链接:https://github.com/spring-projects/spring-data-examples/blob/e953f6d5e5725bce65430d160e430f042c16a8c3/jpa/java8/src/main/java/example/springdata/jpa/java8/AbstractEntity.java(我的编辑请求已被拒绝)。 - Zhenya

9
我在我的项目中使用Java 8、EclipseLink (JPA 2.1)、PostgreSQL 9.3和PostgreSQL驱动程序-Postgresql-9.2-1002.jdbc4.jar,我可以使用新API的LocalDateTime变量而不出现错误,但数据库中列的数据类型将为bytea。据我所知,你只能从Java应用程序中读取它。
但是,你可以使用AttributeConverter将这些类转换为java.sql.Date。我从Java.net中找到了这个代码。
@Converter(autoApply = true)
public class LocalDatePersistenceConverter implements
AttributeConverter {
@Override
public java.sql.Date convertToDatabaseColumn(LocalDate entityValue) {
    return java.sql.Date.valueOf(entityValue);
}

@Override
public LocalDate convertToEntityAttribute(java.sql.Date databaseValue) {
    return databaseValue.toLocalDate();
}

1
您可以使用用户类型进行映射。请参见此链接:https://github.com/spring-projects/spring-data-examples/blob/master/jpa%2Fjava8%2Fsrc%2Fmain%2Fjava%2Fexample%2Fspringdata%2Fjpa%2Fjava8%2FAbstractEntity.java - TheKojuEffect
3
起初看似没有问题,但是数据库中该列的数据类型是bytea,这就出现了问题。日期/时间不应该被序列化! - Neil Stockton
不管你使用转换器转换成java.sql,我指出来了也没用。如果我表达不清楚,对不起。 - Ismael_Diaz

3
我知道这是一个老问题,但我想到了一种可能有用的替代解决方案。
不要试图将新的java.time.*类映射到现有的数据库类型,而是可以利用@Transient注解。
@Entity
public class Person {
    private Long id;        
    private Timestamp createdTimestamp;

    @Id
    @GeneratedValue
    public Long getId() { return id; }

    private Timestamp getCreatedTimestamp() {
        return createdTime;
    }

    private void setCreatedTimestamp(final Timestamp ts) {
        this.createdTimestamp = ts;
    }

    @Transient
    public LocalDateTime getCreatedDateTime() {
        return createdTime.getLocalDateTime();
    }

    public void setCreatedDateTime(final LocalDateTime dt) {
        this.createdTime = Timestamp.valueOf(dt);
    }
}

您使用公共的getter/setter方法来操作新的Java 8日期/时间类,但在幕后,getter/setter方法使用旧的日期/时间类。当您持久化实体时,旧的日期/时间属性将被持久化,但由于新的Java 8属性带有@Transient注释,因此不会持久化新的Java 8属性。


2
你在代码中忘记声明 LocalDateTime createdTime 变量了。 - Ismael_Diaz
3
JPA规范不允许使用私有的getter/setter来操作持久化字段(例如Timestamp),至少应该是受保护的。有些JPA实现会忽略这一要求并且这样做可以工作,但并不符合标准。 - Ismael_Diaz

1
对于类型TIMESTAMP,您可以使用此转换器:

@Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(LocalDateTime datetime) {
        return datetime == null ? null : Timestamp.valueOf(datetime);
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Timestamp timestamp) {
        return timestamp == null ? null : timestamp.toLocalDateTime();
    }

}

对于类型日期,您可以使用此转换器:

@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {

    @Override
    public Date convertToDatabaseColumn(LocalDate date) {
        return date == null ? null : Date.valueOf(date);
    }

    @Override
    public LocalDate convertToEntityAttribute(Date date) {
        return date == null ? null : date.toLocalDate();
    }

}

对于类型 TIME ,您可以使用此转换器:

@Converter(autoApply = true)
public class LocalTimeAttributeConverter implements AttributeConverter<LocalTime, Time> {

    @Override
    public Time convertToDatabaseColumn(LocalTime time) {
        return time == null ? null : Time.valueOf(time);
    }

    @Override
    public LocalTime convertToEntityAttribute(Time time) {
        return time == null ? null : time.toLocalTime();
    }

}

0

有许多方法可以做到这一点,也取决于您的框架: 如果您的框架具有现场转换器,例如Spring,请执行以下操作: 1-

@DateTimeFormat(pattern = "dd.MM.yyyy - HH:mm")
private Long createdDate;

这里我正在使用传统的纪元格式https://www.epochconverter.com/ 纪元是非常灵活和被接受的格式

2- 另一种方法是使用jpa @PostLoad @PreUpdate @PrePersist

@PostLoad
      public void convert() {
        this.jva8Date= LocalDate.now().plusDays(1);
      }

或者使用临时变量

@Transient
public LocalDateTime getCreatedDateTime() {
    return createdTime.getLocalDateTime();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接