JPA Support for Java 8 New Date and Time API

JPA support for Java 8 new date and time API

JPA 2.1 is a spec that came out before Java 1.8, so doesn't mandate any support for it. Obviously some implementations may support some Java 1.8 features. Some have problems with Java 1.8 bytecode (e.g EclipseLink). I know DataNucleus supports java.time and Java 1.8 since that's the one I use. You'd have to check your implementation for what its support level is.

It has been requested that JPA 2.2 support the java.time types, see this issue https://java.net/jira/browse/JPA_SPEC-63

JPA and java 8 date API - choosing correct implementation (Instant, LocalDateTime, ZonedDateTime)

Instant is by by definition UTC, so if you are always dealing with UTC, it is sufficient if it has all of the operations you need. LocalDateTime is a timestamp without a specific time zone or zone offset, but if you know you are only dealing with UTC, it can be useful for the operations it provides that Instant does not.

The most exact type that can carry a definite zone offset, allowing you to preserve the moment of time while converting back and forth between UTC and other time zones is OffsetDateTime. There is a convenient ZoneOffset.UTC constant that you can use to fix the offset at +0 hours for UTC. ZonedDateTime adds the concept of a TimeZone which adds support for things like daylight savings time, a concept which you generally don't need when dealing with UTC unless you want to display the local time in a specific locale.

Java 8 date/time type `java.time.Instant` not supported by default Issue :

Do you have the following artifact into your project ?

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

According to the spring source code, if the class com.fasterxml.jackson.datatype.jsr310.JavaTimeModule is present in your classpath, the JavaTimeModule should be registered

How to use the Java 8 LocalDateTime with JPA and Hibernate

Since Hibernate 4 doesn't support it you need to implement a user type as shown in this example.

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.usertype.EnhancedUserType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

public class LocalDateTimeUserType implements EnhancedUserType, Serializable {

private static final int[] SQL_TYPES = new int[]{Types.TIMESTAMP};

@Override
public int[] sqlTypes() {
return SQL_TYPES;
}

@Override
public Class returnedClass() {
return LocalDateTime.class;
}

@Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
}
if (x == null || y == null) {
return false;
}
LocalDateTime dtx = (LocalDateTime) x;
LocalDateTime dty = (LocalDateTime) y;
return dtx.equals(dty);
}

@Override
public int hashCode(Object object) throws HibernateException {
return object.hashCode();
}

@Override
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Object timestamp = StandardBasicTypes.TIMESTAMP.nullSafeGet(resultSet, names, session, owner);
if (timestamp == null) {
return null;
}
Date ts = (Date) timestamp;
Instant instant = Instant.ofEpochMilli(ts.getTime());
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}

@Override
public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if (value == null) {
StandardBasicTypes.TIMESTAMP.nullSafeSet(preparedStatement, null, index, session);
} else {
LocalDateTime ldt = ((LocalDateTime) value);
Instant instant = ldt.atZone(ZoneId.systemDefault()).toInstant();
Date timestamp = Date.from(instant);
StandardBasicTypes.TIMESTAMP.nullSafeSet(preparedStatement, timestamp, index, session);
}
}

@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}

@Override
public boolean isMutable() {
return false;
}

@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}

@Override
public Object assemble(Serializable cached, Object value) throws HibernateException {
return cached;
}

@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}

@Override
public String objectToSQLString(Object object) {
throw new UnsupportedOperationException();
}

@Override
public String toXMLString(Object object) {
return object.toString();
}

@Override
public Object fromXMLString(String string) {
return LocalDateTime.parse(string);
}

}

The new usertype can then be used in the mapping with the @Type annotation. For e.g.

@Type(type="com.hibernate.samples.type.LocalDateTimeUserType")
@Column(name = "invalidate_token_date")
private LocalDateTime invalidateTokenDate;

The @Type annotation needs a full path to the class that implements the userType interface; this is the factory for producing the target type of the mapped column.

Here's how to do the same thing in JPA2.1



Related Topics



Leave a reply



Submit