How to force Hibernate to return dates as java.util.Date instead of Timestamp?

前端 未结 7 921
天涯浪人
天涯浪人 2020-12-04 08:13

Situation:

I have a persistable class with variable of java.util.Date type:

import java.util.Date;

@Entity
@Table(name = \"prd_peri         


        
7条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-04 08:40

    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.

提交回复
热议问题