JPA 2.1:Java 8日期/时间API简介

我想在我的JPA应用程序中添加Java 8 Date / Time API(JSR-310)的支持。

很明显, JPA 2.1 不支持Java 8 Date / Time API 。
作为一种解决方法,最常见的建议是使用AttributeConverter

在我现有的应用程序中,我将实体更改为使用LocalDate / LocalDateTime类型作为列映射字段,并为它们添加了java.util.Date旧setter / getter。
我创建了相应的AttributeConverter类。

Query.setParameter()java.util.Date实例一起使用时, 我的应用程序现在失败了 (它在转换到新API之前工作)。
似乎JPA期望新的日期类型并且不会动态转换它。

我希望如果将一个参数传递给已注册AttributeConverter的类型的setParameter() ,它将由转换器自动转换。 但似乎并非如此,至少不使用EclipseLink 2.6.2

 java.lang.IllegalArgumentException: You have attempted to set a value of type class java.util.Date for parameter closeDate with expected type of class java.time.LocalDate from query string SELECT obj FROM [...] at org.eclipse.persistence.internal.jpa.QueryImpl.setParameterInternal(QueryImpl.java:937) ~[eclipselink-2.6.2.jar:2.6.2.v20151217-774c696] at org.eclipse.persistence.internal.jpa.EJBQueryImpl.setParameter(EJBQueryImpl.java:593) ~[eclipselink-2.6.2.jar:2.6.2.v20151217-774c696] at org.eclipse.persistence.internal.jpa.EJBQueryImpl.setParameter(EJBQueryImpl.java:1) ~[eclipselink-2.6.2.jar:2.6.2.v20151217-774c696] [...] 

问题

  1. 这种行为有望吗? 我错过了什么?
  2. 有没有办法不破坏现有代码的情况下将新日期类型用作字段?
  3. 您是如何处理 JPA中新的Date / Time API过渡的?

更新:

但是,似乎至少使用EclipseLink,并不完全支持AttributeConverter存在的自定义类型:

在JPQL查询中, 实际字段类型和转换后的数据库类型都不能用作参数 。 使用转换的数据库类型时,会发生上述exception。 当使用实际字段类型(例如LocalDate )时,它会直接传递给不知道此类型的jdbc驱动程序:

 Caused by: java.sql.SQLException: Invalid column type at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:10495) at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:9974) at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:10799) at oracle.jdbc.driver.OraclePreparedStatement.setObject(OraclePreparedStatement.java:10776) at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:241) at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.setParameterValueInDatabaseCall(DatabasePlatform.java:2506) 

我希望EclipseLink使用AttributeConverter将字段类型转换为java.sql类型。 (另见此错误报告: https : //bugs.eclipse.org/bugs/show_bug.cgi?id = 494999 )

这引出了我们最重要的问题#4:

  1. 是否有使用EclipseLink支持java 8日期字段的变通方法/解决方案, 包括在这样的字段上使用查询参数的可能性

附加信息

  • 使用AttributeConverter(用于LocalDateTime转换)
  • 重现exception的其他信息

前段时间,我将Java EE 7 Web应用程序从Java 7转换为Java 8,并用LocalDateLocalDateTime替换了实体java.util.Date

  1. 是的,这种行为是预期的,因为AttributeConverter只在用于实体字段的类型数据库类型 (即java.sql.Date等)之间进行转换; 它不在实体字段类型和查询参数java.util.Date使用的java.util.Date之间进行转换。
  2. 据我所知,不,在将java.time类型引入JPA实体之后,没有办法在现有代码中继续使用java.util.Date
  3. 除了创建必要的AttributeConverter实现之外,我java.time所有出现的java.util.Date更改为适当的java.time类型,不仅在实体中,而且在JPA-QL查询和业务方法中。

对于第2项,当然你可以通过使用实用程序方法和在java.utiljava.time之间进行转换的getter / setter来实现java.time ,但它不会一直都是这样。 更重要的是,如果您不愿意转换使用这些属性的其余代码,我不太明白将java.time类型引入JPA实体属性的重点。 在我在那个Java EE应用程序中完成的转换工作之后,没有使用java.util.Date留在任何地方(尽管我还必须为JSF创建转换器)。

由于提供程序本身存在很多错误,我认为你没有太多选择,只能在映射级别使用java.util.Date ,在API级别使用java.util.Date

假设您编写了一个实用程序类,用于转换到/来自名为DateUtils java.util日期 ,您可以按如下方式定义映射:

 @Entity public class MyEntity { @Column("DATE") private Date date; // java.util.Date public void setDate(LocalDateTime date) { this.date = DateUtils.convertToDate(date); } public LocalDateTime getDate() { return DateUtils.convertFromDate(date); } } 

然后在JPQL中按date过滤:

 public List readByDateGreaterThan(LocalDateTime date) { Query query = em.createQuery("select e from MyEntity e where e.date > :date"); query.setParameter("date", DateTuils.convertToDate(date)); return query.getResultList(); } 

因此, java.util日期将在内部用于实体和DAO(存储库),而实体和DAO公开的API将接受/返回java 8日期,从而使应用程序的其余部分仅使用java 8日期。

我有以下设置:

  • EclipseLink v2.6.2
  • h2数据库v1.4.191
  • Java 8

实体类如下:

 @Entity public class MeasuringPoint extends BaseEntity { @Column(nullable = false) private LocalDateTime when; public void setWhen(LocalDateTime when) { this.when = when; } public LocalDateTime getWhen() { return when; } } 

JPA 2.1所需的转换器是:

 @Converter(autoApply = true) public class LocalDateTimeConverter implements AttributeConverter { @Override public Timestamp convertToDatabaseColumn(LocalDateTime attribute) { return attribute == null ? null : Timestamp.valueOf(attribute); } @Override public LocalDateTime convertToEntityAttribute(Timestamp dbData) { return dbData == null ? null : dbData.toLocalDateTime(); } } 

现在我可以进行查询

 List result = em.createQuery( "SELECT p FROM MeasuringPoint p WHERE p.when = :custDate") .setParameter("custDate", LocalDateTime.now()) .getResultList(); 

它就像一个魅力。 结果包含预期的实体。 转换为TIMESTAMP是自动完成的。 当您使用Criteria API进行查询时,请查看此答案 , 该答案显示如何在Criteria API查询中使用LocalDateTime

我想知道为什么它不适用于你的代码。 也许H2 JDBC驱动程序确实支持Oracle没有的东西。

AttributeConverter按设计工作,因为转换器用于处理实体类型和数据库类型之间的来回。 validation是validation参数的类型与实体中的类型不匹配 – 只是因为属性转换器可以处理它,并不意味着它符合属性转换器类型的约定。 JPA只说它会在转到数据库之前通过转换器,在这种情况下它不会进入数据库。

如果您不喜欢Rogério的建议,您可以1)修改EclipseLink代码以放宽validation以允许它进入您的转换器,或2)将您的属性类型更改为’Object’,以便您可以使用所有参数类型传入将转到您的转换器。