Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA 2.1: Introducing Java 8 Date/Time API

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:

  1. Is this behavior expected? Did I miss something?
  2. Is there a way to use the new date types as fields without breaking existing code?
  3. 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:

  1. 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

  • AttributeConverter used (for LocalDateTime conversion)
  • Additional information to reproduce exception
like image 649
MRalwasser Avatar asked May 11 '16 14:05

MRalwasser


People also ask

Can you provide some APIs of Java 8 date and time?

Java 8 provides APIs for the easy formatting of Date and Time: LocalDateTime localDateTime = LocalDateTime. of(2015, Month. JANUARY, 25, 6, 30);

How will you get current date and time using Java 8 date and time API?

A toInstant() method is added to the original Date and Calendar objects, which can be used to convert them to the new Date-Time API. Use an ofInstant(Insant,ZoneId) method to get a LocalDateTime or ZonedDateTime object. Let us see them in action.

Are you aware of date and time API introduced Java 8?

New date-time API is introduced in Java 8 to overcome the following drawbacks of old date-time API : Not thread safe : Unlike old java. util. Date which is not thread safe the new date-time API is immutable and doesn't have setter methods.

What is an instant in Java 8 date and time API?

The Java Date Time API was added from Java version 8. instant() method of Clock class returns a current instant of Clock object as Instant Class Object. Instant generates a timestamp to represent machine time. So this method generates a timestamp for clock object.


2 Answers

Some time ago, I converted a Java EE 7 web app from Java 7 to Java 8, and replaced java.util.Date in entities with LocalDate and LocalDateTime.

  1. Yes, that behavior is expected, because an 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 a java.util.Date used in a query parameter.
  2. As far as I know, no, there is no way to continue using java.util.Date in existing code, after introducing the java.time types into JPA entities.
  3. Besides creating the necessary AttributeConverter implementations, I changed all occurrences of java.util.Date to the appropriate java.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 and java.time, but it's not going to go all the way. More importantly, I don't quite see the point of introducing java.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 of java.util.Date left anywhere (though I also had to create converters for JSF).

like image 60
Rogério Avatar answered Oct 07 '22 09:10

Rogério


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 called DateUtils, you could define your mappings as follows:

@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);
  }
}

Then to filter by date in JPQL:

public List<MyEntity> 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();
}

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.

like image 29
Dragan Bozanovic Avatar answered Oct 07 '22 10:10

Dragan Bozanovic