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.