Java8 introduces new date and time types which are better than java.lang.Date. Unfortunately though, the latest Hibernate (4.2.7.SP1 at the time of writing) does not support these types.
Here are some classes you can use to serialize these:
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); } } |
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.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; public class LocalDateUserType implements EnhancedUserType, Serializable { private static final int[] SQL_TYPES = new int[]{Types.DATE}; @Override public int[] sqlTypes() { return SQL_TYPES; } @Override public Class returnedClass() { return LocalDate.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { if (x == y) { return true; } if (x == null || y == null) { return false; } LocalDate dtx = (LocalDate) x; LocalDate dty = (LocalDate) 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.DATE.nullSafeGet(resultSet, names, session, owner); if (timestamp == null) { return null; } Date date = (Date) timestamp; Instant instant = Instant.ofEpochMilli(date.getTime()); return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate(); } @Override public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { if (value == null) { StandardBasicTypes.DATE.nullSafeSet(preparedStatement, null, index, session); } else { LocalDate ld = ((LocalDate) value); Instant instant = ld.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(); Date time = Date.from(instant); StandardBasicTypes.DATE.nullSafeSet(preparedStatement, time, 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 LocalDate.parse(string); } } |
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.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.Date; public class LocalTimeUserType implements EnhancedUserType, Serializable { private static final int[] SQL_TYPES = new int[]{Types.TIME}; private static final int A_YEAR = 2000; private static final int A_MONTH = 1; private static final int A_DAY = 1; @Override public int[] sqlTypes() { return SQL_TYPES; } @Override public Class returnedClass() { return LocalTime.class; } @Override public boolean equals(Object x, Object y) throws HibernateException { if (x == y) { return true; } if (x == null || y == null) { return false; } LocalTime dtx = (LocalTime) x; LocalTime dty = (LocalTime) 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.TIME.nullSafeGet(resultSet, names, session, owner); if (timestamp == null) { return null; } Date time = (Date) timestamp; Instant instant = Instant.ofEpochMilli(time.getTime()); return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalTime(); } @Override public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { if (value == null) { StandardBasicTypes.TIME.nullSafeSet(preparedStatement, null, index, session); } else { LocalTime lt = ((LocalTime) value); Instant instant = lt.atDate(LocalDate.of(A_YEAR, A_MONTH, A_DAY)). atZone(ZoneId.systemDefault()).toInstant(); Date time = Date.from(instant); StandardBasicTypes.TIME.nullSafeSet(preparedStatement, time, 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 LocalTime.parse(string); } } |
To use these, you can put the following configuration in your package-info.java file.
@TypeDefs({ @TypeDef(name = "localDateType", defaultForType = LocalDate.class, typeClass = LocalDateUserType.class), @TypeDef(name = "localDateTimeType", defaultForType = LocalDateTime.class, typeClass = LocalDateTimeUserType.class), @TypeDef(name = "localTimeType", defaultForType = LocalTime.class, typeClass = LocalTimeUserType.class) }) package your.domain.pkg; |
Make sure you include the package in both the packagesToScan and annotatedPackages configuration in Hibernate.
What I think is somewhat disturbing is that even though Java 8 is now released and large numbers of new projects are presumably starting to use these types in preference to the old (and somewhat broken) java.util.Date/java.sql.Date/etc, and that the enhancement is pretty simple, it looks as though nobody involved in the Hibernate project has even started work on adding support for them. See https://hibernate.atlassian.net/browse/HHH-8844
You, Sir, are a star. That problem was doing my box in, so it was.
Awesome! 😀 Thanks for sharing!
Congratulations! Resolved!
[…] The next option is to use a Hibernate user type (a blog post about converting other JSR-310 classes here). […]