How to use the Java 8 LocalDateTime with JPA and Hibernate
Solution 1
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
Solution 2
For any Hibernate 5.x users, there is
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>5.0.0.Final</version>
</dependency>
You don't need to do anything else. Just add the dependency, and the Java 8 time types should work like any other basic types, no annotations required.
private LocalDateTime invalidateTokenDate;
Note: this won't save to timestamp
type though. Testing with MySQL, it saves to datetime
type.
Solution 3
Since version 2.2, JPA offers support for mapping Java 8 Date/Time API, like LocalDateTime
, LocalTime
, LocalDateTimeTime
, OffsetDateTime
or OffsetTime
.
Also, even with JPA 2.1, Hibernate 5.2 supports all Java 8 Date/Time API by default.
In Hibernate 5.1 and 5.0, you have to add the hibernate-java8
Maven dependency.
So, let's assume we have the following entity:
@Entity(name = "UserAccount")
@Table(name = "user_account")
public class UserAccount {
@Id
private Long id;
@Column(name = "first_name", length = 50)
private String firstName;
@Column(name = "last_name", length = 50)
private String lastName;
@Column(name = "subscribed_on")
private LocalDateTime subscribedOn;
//Getters and setters omitted for brevity
}
Notice that the subscribedOn
attribute is a LocalDateTime
Java object.
When persisting the UserAccount
:
UserAccount user = new UserAccount()
.setId(1L)
.setFirstName("Vlad")
.setLastName("Mihalcea")
.setSubscribedOn(
LocalDateTime.of(
2020, 5, 1,
12, 30, 0
)
);
entityManager.persist(user);
Hibernate generates the proper SQL INSERT statement:
INSERT INTO user_account (
first_name,
last_name,
subscribed_on,
id
)
VALUES (
'Vlad',
'Mihalcea',
'2020-05-01 12:30:00.0',
1
)
When fetching the UserAccount
entity, we can see that the LocalDateTime
is properly fetched from the database:
UserAccount userAccount = entityManager.find(
UserAccount.class, 1L
);
assertEquals(
LocalDateTime.of(
2020, 5, 1,
12, 30, 0
),
userAccount.getSubscribedOn()
);
Solution 4
If you can use Java EE 7, there is more elegant solution:
>> Implement this:
@Converter(autoApply = true)
public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Date> {
@Override
public Date convertToDatabaseColumn(LocalDateTime date) {
if (date == null){
return null;
}
return date.toDate();
}
@Override
public LocalDateTime convertToEntityAttribute(Date value) {
if (value == null) {
return null;
}
return LocalDateTime.fromDateFields(value);
}
}
>> Use like this:
...
@Column(name = "invalidate_token_date")
private LocalDateTime invalidateTokenDate;
....
Value (autoApply = true)
means that @Converter
is automatically used for conversion of every LocalDateTime
property in your JPA Entity.
Btw, AttributeConverter
is pretty good for mapping Enums too.
Related videos on Youtube
gstackoverflow
Updated on January 09, 2021Comments
-
gstackoverflow over 3 years
I have the following class description snippet:
... @Column(name = "invalidate_token_date") @Temporal(TemporalType.TIMESTAMP) private LocalDateTime invalidateTokenDate; ....
This code doesn't work on Hibernate 4 because
@Temporal
doesn't supportLocalDateTime.
I saw the suggestion on how to use LocalDateTime from Joda-Time but I use Java 8.
-
gstackoverflow over 9 yearsAnd how should I change mapping after adding class from you answer?
-
obesechicken13 almost 9 yearsWhich library gives you the localDateTime toDate and fromDateFields methods?
-
obesechicken13 almost 9 yearsFound a converter here: stackoverflow.com/questions/19431234/…
-
Quentin Klein over 8 yearsSaved my night :) Thanks
-
Radosław Osiński over 8 yearsThat is not enough for spring configuration. After adding hibernate-java8 dependency to project ClassCastException still occurs.
-
Peter DeGregorio about 8 yearsThank you. Adding hibernate-java8 artifact worked for me with Spring 4.2.5 and Hibernate 5.1.31. Was getting org.hibernate.type.SerializationException: could not deserialize on the entity with LocalDateTime fields.
-
Jeroen van Dijk-Jun about 7 yearsThe question is about Hibernate4, so this is a good idea, but no answer for this question
-
Bruno Gasparotto over 6 yearsIt's actually a JPA feature, which also works with Spring Data JPA, not only with Java EE.
-
Gwaptiva over 6 yearsNote that if you convert this to store
java.util.LocalDate
fields, you cannot go via thejava.sql.Date.toInstant()
method, as that throws an exception; usetoLocalDate
instead. -
Viktor Vix Jančík about 6 yearsNo longer needed in Hibernate 5.3
-
deFreitas over 5 yearsYou sir saved my day, working perfectly on postgres 10 timestamp type