I'd like to add support for the Java 8 Date/Time API (JSR-310) in my JPA-enabled application.
It's clear that JPA 2.1 does not support the Java 8 Date/Time API.
As a workaround, the most common advise is to use an AttributeConverter
.
In my existing application, I changed my entities to use LocalDate
/LocalDateTime
types for the column mapping fields and added legacy setter/getters for java.util.Date
to them.
I created corresponding AttributeConverter
classes.
My application does now fail when using Query.setParameter()
with java.util.Date
instances (it worked before the transition to the new API).
It seems that JPA expects the new date types and does not convert it on the fly.
I expected that if passing an argument to setParameter()
of a type for which an AttributeConverter
has been registered, it would be automatically converted by the converter.
But this seems to be not the case, at least not using 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]
[...]
Questions:
- Is this behavior expected? Did I miss something?
- Is there a way to use the new date types as fields without breaking existing code?
- How did you deal with the transition to the new Date/Time API within JPA?
UPDATE:
However, It seems that at least using EclipseLink, custom types for which an AttributeConverter
exists, are not fully supported:
Within JPQL queries, neither the actual field type nor the converted database type can be used as a parameter.
When using the converted database type, the exception described above occurs.
When using the actual field type (e.g. LocalDate
), it's directly passed to the jdbc driver which doesn't know this type:
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)
I would expect that EclipseLink converts the field type to the java.sql type using the AttributeConverter. (see also this bug report: https://bugs.eclipse.org/bugs/show_bug.cgi?id=494999 )
Which leads us to the most important question #4:
- Is there a workaround/solution to support java 8 date fields using
EclipseLink
, including the possibility to use a query parameters on such a field?
ADDITIONAL INFO
With so many bugs in the provider itself I don't think you have much choice but to use
java.util.Date
on the mapping level and java 8 dates on the API level.Assuming you write a utility class for conversion to/from
java.util
dates calledDateUtils
, you could define your mappings as follows:Then to filter by
date
in JPQL:So,
java.util
dates would be used in entities and DAOs (Repositories) internally, while the API exposed by entities and DAOs would take/return java 8 dates, thus enabling the rest of the application to operate with java 8 dates only.AttributeConverter is working as designed, as the converter is meant to handle the back and forth between your entity type and the database type. Validation is verifying the type of the parameter doesn't match the type within the entity - just because your attribute converter can handle it, doesn't mean it fits the contract of the attribute converter types. JPA only says it will go through the converter before going to the database, and in this case it doesn't go to the database.
If you don't like Rogério's suggestions, you can 1) modify the EclipseLink code to relax the validation to allow it to go through to your converter, or 2) change your attribute type to 'Object' instead so that all parameter types you may pass in will go to your converter.
I have the following setup:
The entity class is like:
The required converter for JPA 2.1 is:
Now I can make a query
and it works like a charm. The result contains the expected entities. The conversion to TIMESTAMP is done automatically. When you have queries using the Criteria API, have a look at this answer which shows how to use
LocalDateTime
within Criteria API queries.I wonder why it doesn't work with your code. Maybe the H2 JDBC driver does support something your Oracle doesn't.
Some time ago, I converted a Java EE 7 web app from Java 7 to Java 8, and replaced
java.util.Date
in entities withLocalDate
andLocalDateTime
.AttributeConverter
only converts between the type used for entity fields and the database type (ie,java.sql.Date
, etc.); it does not convert between an entity field type and ajava.util.Date
used in a query parameter.java.util.Date
in existing code, after introducing thejava.time
types into JPA entities.AttributeConverter
implementations, I changed all occurrences ofjava.util.Date
to the appropriatejava.time
types, not only in entities but also in JPA-QL queries and in business methods.For item 2, of course you can go some way by using utility methods and getters/setters that convert between
java.util
andjava.time
, but it's not going to go all the way. More importantly, I don't quite see the point of introducingjava.time
types into JPA entity attributes, if you are not willing to convert the remaining code that uses these attributes. After the conversion work I did in that Java EE app, there were no uses ofjava.util.Date
left anywhere (though I also had to create converters for JSF).