How to force Hibernate to return dates as java.util.Date instead of Timestamp?
Solution 1:
A simple alternative to using a custom UserType is to construct a new java.util.Date in the setter for the date property in your persisted bean, eg:
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Column;
@Entity
public class Purchase {
private Date date;
@Column
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
// force java.sql.Timestamp to be set as a java.util.Date
this.date = new Date(date.getTime());
}
}
Solution 2:
So, I spent some time with this issue and found a solution. It is not pretty one, but at least a start point - maybe someone will supplement this with some useful comments.
Some info about mapping that I found in process:
-
Class that contains basic mapping of Hibernate types to property types is org.hibernate.type.TypeFactory. All this mappings are stored in unmodifiable map
private static final Map BASIC_TYPES; ... basics.put( java.util.Date.class.getName(), Hibernate.TIMESTAMP ); ... BASIC_TYPES = Collections.unmodifiableMap( basics );
As you can see with java.util.Date type assosited with Hibernate type org.hibernate.type.TimestampType
-
Next interesting moment - creation of Hibernate org.hibernate.cfg.Configuration - object that contains all info about mapped classes. This classes and their properties can be extracted like this:
Iterator clsMappings = cfg.getClassMappings(); while(clsMappings.hasNext()){ PersistentClass mapping = (PersistentClass) clsMappings.next(); handleProperties(mapping.getPropertyIterator(), map); }
-
Vast majority of properties are the objects of org.hibernate.mapping.SimpleValue types. Our point of interest is the method SimpleValue.getType() - in this method is defined what type will be used to convert properties values back-and-forth while working with DB
Type result = TypeFactory.heuristicType(typeName, typeParameters);
At this point I understand that I am unable to modify BASIC_TYPES - so the only way - to replace SimpleValue object to the properties of java.util.Date types to my custom Object that will be able to know the exact type to convert.
The solution:
-
Create custom container entity manager factory by extending HibernatePersistence class and overriding its method createContainerEntityManagerFactory:
public class HibernatePersistenceExtensions extends HibernatePersistence { @Override public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map map) { if ("true".equals(map.get("hibernate.use.custom.entity.manager.factory"))) { return CustomeEntityManagerFactoryFactory.createCustomEntityManagerFactory(info, map); } else { return super.createContainerEntityManagerFactory(info, map); } } }
-
Create Hibernate configuration object, modify value ojects for java.util.Date properties and then create custom entity manager factory.
public class ReattachingEntityManagerFactoryFactory { @SuppressWarnings("rawtypes") public static EntityManagerFactory createContainerEntityManagerFactory( PersistenceUnitInfo info, Map map) { Ejb3Configuration cfg = new Ejb3Configuration(); Ejb3Configuration configured = cfg.configure( info, map ); handleClassMappings(cfg, map); return configured != null ? configured.buildEntityManagerFactory() : null; } @SuppressWarnings("rawtypes") private static void handleClassMappings(Ejb3Configuration cfg, Map map) { Iterator clsMappings = cfg.getClassMappings(); while(clsMappings.hasNext()){ PersistentClass mapping = (PersistentClass) clsMappings.next(); handleProperties(mapping.getPropertyIterator(), map); } } private static void handleProperties(Iterator props, Map map) { while(props.hasNext()){ Property prop = (Property) props.next(); Value value = prop.getValue(); if (value instanceof Component) { Component c = (Component) value; handleProperties(c.getPropertyIterator(), map); } else { handleReturnUtilDateInsteadOfTimestamp(prop, map); } } private static void handleReturnUtilDateInsteadOfTimestamp(Property prop, Map map) { if ("true".equals(map.get("hibernate.return.date.instead.of.timestamp"))) { Value value = prop.getValue(); if (value instanceof SimpleValue) { SimpleValue simpleValue = (SimpleValue) value; String typeName = simpleValue.getTypeName(); if ("java.util.Date".equals(typeName)) { UtilDateSimpleValue udsv = new UtilDateSimpleValue(simpleValue); prop.setValue(udsv); } } } } }
As you can see I just iterate over every property and substitute SimpleValue-object for UtilDateSimpleValue for properties of type java.util.Date. This is very simple class - it implements the same interface as SimpleValue object, e.g org.hibernate.mapping.KeyValue. In constructor original SimpleValue object is passed - so every call to UtilDateSimpleValue is redirected to the original object with one exception - method getType(...) return my custom Type.
public class UtilDateSimpleValue implements KeyValue{
private SimpleValue value;
public UtilDateSimpleValue(SimpleValue value) {
this.value = value;
}
public SimpleValue getValue() {
return value;
}
@Override
public int getColumnSpan() {
return value.getColumnSpan();
}
...
@Override
public Type getType() throws MappingException {
final String typeName = value.getTypeName();
if (typeName == null) {
throw new MappingException("No type name");
}
Type result = new UtilDateUserType();
return result;
}
...
}
-
And the last step is implementation of UtilDateUserType. I just extend original org.hibernate.type.TimestampType and override its method get() like this:
public class UtilDateUserType extends TimestampType{ @Override public Object get(ResultSet rs, String name) throws SQLException { Timestamp ts = rs.getTimestamp(name); Date result = null; if(ts != null){ result = new Date(ts.getTime()); } return result; } }
That is all. A little bit tricky, but now every java.util.Date property is returned as java.util.Date without any additional modifications of existing code (annotations or modifying setters). As I find out in Hibernate 4 or above there is a much more easier way to substitute your own type (see details here: Hibernate TypeResolver). Any suggestions or criticism are welcome.
Solution 3:
Approaches 1 and 2 obviously don't work, because you get java.sql.Date
objects, per JPA/Hibernate spec, and not java.util.Date
. From approaches 3 and 4, I would rather choose the latter one, because it's more declarative, and will work with both field and getter annotations.
You have already laid out the solution 4 in your referenced blog post, as @tscho was kind to point out. Maybe defaultForType (see below) should give you the centralized solution you were looking for. Of course will will still need to differentiate between date (without time) and timestamp fields.
For future reference I will leave the summary of using your own Hibernate UserType here:
To make Hibernate give you java.util.Date
instances, you can use the @Type and @TypeDef annotations to define a different mapping of your java.util.Date java types to and from the database.
See the examples in the core reference manual here.
- Implement a UserType to do the actual plumbing (conversion to/from java.util.Date), named e.g.
TimestampAsJavaUtilDateType
-
Add a @TypeDef annotation on one entity or in a package-info.java - both will be available globally for the session factory (see manual link above). You can use defaultForType to apply the type conversion on all mapped fields of type
java.util.Date
.@TypeDef name = "timestampAsJavaUtilDate", defaultForType = java.util.Date.class, /* applied globally */ typeClass = TimestampAsJavaUtilDateType.class )
-
Optionally, instead of
defaultForType
, you can annotate your fields/getters with @Type individually:@Entity public class MyEntity { [...] @Type(type="timestampAsJavaUtilDate") private java.util.Date myDate; [...] }
P.S. To suggest a totally different approach: we usually just don't compare Date objects using equals() anyway. Instead we use a utility class with methods to compare e.g. only the calendar date of two Date instances (or another resolution such as seconds), regardless of the exact implementation type. That as worked well for us.
Solution 4:
Here is solution for Hibernate 4.3.7.Final.
pacakge-info.java contains
@TypeDefs(
{
@TypeDef(
name = "javaUtilDateType",
defaultForType = java.util.Date.class,
typeClass = JavaUtilDateType.class
)
})
package some.pack;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
And JavaUtilDateType:
package some.other.or.same.pack;
import java.sql.Timestamp;
import java.util.Comparator;
import java.util.Date;
import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.LiteralType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.VersionType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JdbcTimestampTypeDescriptor;
import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor;
/**
* Note: Depends on hibernate implementation details hibernate-core-4.3.7.Final.
*
* @see
* <a href="http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch06.html#types-custom">Hibernate
* Documentation</a>
* @see TimestampType
*/
public class JavaUtilDateType
extends AbstractSingleColumnStandardBasicType<Date>
implements VersionType<Date>, LiteralType<Date> {
public static final TimestampType INSTANCE = new TimestampType();
public JavaUtilDateType() {
super(
TimestampTypeDescriptor.INSTANCE,
new JdbcTimestampTypeDescriptor() {
@Override
public Date fromString(String string) {
return new Date(super.fromString(string).getTime());
}
@Override
public <X> Date wrap(X value, WrapperOptions options) {
return new Date(super.wrap(value, options).getTime());
}
}
);
}
@Override
public String getName() {
return "timestamp";
}
@Override
public String[] getRegistrationKeys() {
return new String[]{getName(), Timestamp.class.getName(), java.util.Date.class.getName()};
}
@Override
public Date next(Date current, SessionImplementor session) {
return seed(session);
}
@Override
public Date seed(SessionImplementor session) {
return new Timestamp(System.currentTimeMillis());
}
@Override
public Comparator<Date> getComparator() {
return getJavaTypeDescriptor().getComparator();
}
@Override
public String objectToSQLString(Date value, Dialect dialect) throws Exception {
final Timestamp ts = Timestamp.class.isInstance(value)
? (Timestamp) value
: new Timestamp(value.getTime());
// TODO : use JDBC date literal escape syntax? -> {d 'date-string'} in yyyy-mm-dd hh:mm:ss[.f...] format
return StringType.INSTANCE.objectToSQLString(ts.toString(), dialect);
}
@Override
public Date fromStringValue(String xml) throws HibernateException {
return fromString(xml);
}
}
This solution mostly relies on TimestampType implementation with adding additional behaviour through anonymous class of type JdbcTimestampTypeDescriptor.