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] [...]
问题 :
- 这种行为有望吗? 我错过了什么?
- 有没有办法在不破坏现有代码的情况下将新日期类型用作字段?
- 您是如何处理 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:
- 是否有使用
EclipseLink
支持java 8日期字段的变通方法/解决方案, 包括在这样的字段上使用查询参数的可能性 ?
附加信息
- 使用AttributeConverter(用于LocalDateTime转换)
- 重现exception的其他信息
前段时间,我将Java EE 7 Web应用程序从Java 7转换为Java 8,并用LocalDate
和LocalDateTime
替换了实体java.util.Date
。
- 是的,这种行为是预期的,因为
AttributeConverter
只在用于实体字段的类型和数据库类型 (即java.sql.Date
等)之间进行转换; 它不在实体字段类型和查询参数java.util.Date
使用的java.util.Date
之间进行转换。 - 据我所知,不,在将
java.time
类型引入JPA实体之后,没有办法在现有代码中继续使用java.util.Date
。 - 除了创建必要的
AttributeConverter
实现之外,我java.time
所有出现的java.util.Date
更改为适当的java.time
类型,不仅在实体中,而且在JPA-QL查询和业务方法中。
对于第2项,当然你可以通过使用实用程序方法和在java.util
和java.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’,以便您可以使用所有参数类型传入将转到您的转换器。