java - 如何强制 Hibernate 将日期返回为 java.util.Date 而不是 Timestamp?

标签 java hibernate jpa mapping

情况:

我有一个带有 java.util.Date 类型变量的持久类:

import java.util.Date;

@Entity
@Table(name = "prd_period")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Period extends ManagedEntity implements Interval {

   @Column(name = "startdate_", nullable = false)
   private Date startDate;

}

DB中对应的表:

CREATE TABLE 'prd_period' (
  'id_' bigint(20) NOT NULL AUTO_INCREMENT,
 ...
  'startdate_' datetime NOT NULL
)

然后我将 Period 对象保存到 DB:

Period p = new Period();
Date d = new Date();
p.setStartDate();
myDao.save(p);

之后,如果我尝试从 DB 中提取我的对象,它会返回 Timestamp 类型的变量 startDate - 我尝试使用 equals(...) 的所有地方都返回 false。

问题:是否有任何方法可以强制 Hibernate 返回日期作为 java.util.Date 类型的对象而不是 Timestamp 而无需显式修改每个此类变量(例如,它必须能够正常工作,没有显式修改 java.util.Date 类型的现有变量)?

注意:

我发现了许多显式解决方案,其中使用了注释或修改了 setter - 但我有许多带有日期变量的类 - 所以我需要一些集中的解决方案,而下面描述的所有内容都不够好:

  1. 使用注解@Type: - 将返回java.sql.Date

    @Column
    @Type(type="date")
    private Date startDate;
    
  2. 使用注解 @Temporal(TemporalType.DATE) - 将返回 java.sql.Date

    @Temporal(TemporalType.DATE)
    @Column(name=”CREATION_DATE”)
    private Date startDate;
    
  3. 通过修改 setter(深拷贝) - java.util.Date 将被返回

    public void setStartDate(Date startDate) {
        if (startDate != null) {
            this.startDate = new Date(startDate.getTime());
        } else {
            this.startDate = null;
        }
    }
    
  4. 通过创建我自己的类型: - java.util.Date 将被返回

这里给出了详细信息: http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/

最佳答案

所以,我花了一些时间来解决这个问题并找到了解决方案。它不是很好,但至少是一个起点——也许有人会用一些有用的评论来补充它。

我在过程中发现的一些关于映射的信息:

  • 包含 Hibernate 类型到属性类型的基本映射的类是 org.hibernate.type.TypeFactory。所有这些映射都存储在不可修改的映射中

    private static final Map BASIC_TYPES;
    ...
    basics.put( java.util.Date.class.getName(), Hibernate.TIMESTAMP );
    ...
    BASIC_TYPES = Collections.unmodifiableMap( basics );
    

你可以看到 java.util.Date 类型与 Hibernate 类型 org.hibernate.type.TimestampType 相关

  • 下一个有趣的时刻 - 创建 Hibernate org.hibernate.cfg.Configuration - 包含有关映射类的所有信息的对象。可以像这样提取此类及其属性:

    Iterator clsMappings = cfg.getClassMappings();
    while(clsMappings.hasNext()){
        PersistentClass mapping = (PersistentClass) clsMappings.next();
        handleProperties(mapping.getPropertyIterator(), map);
    }
    
  • 绝大多数属性都是 org.hibernate.mapping.SimpleValue 类型的对象。我们的兴趣点是 SimpleValue.getType() 方法——在这个方法中定义了在使用 DB 时将使用什么类型来来回转换属性值

    Type result = TypeFactory.heuristicType(typeName, typeParameters);
    

此时我明白我无法修改 BASIC_TYPES - 所以唯一的方法 - 将 SimpleValue 对象替换为 java.util.Date 类型的属性到我的自定义对象,该对象将能够知道要转换的确切类型.

解决办法:

  • 通过扩展 HibernatePersistence 类并覆盖其方法 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);
            }
        }
    }
    
  • 创建 Hibernate 配置对象,修改 java.util.Date 属性的值对象,然后创建自定义实体管理器工厂。

    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);
                    }
                }
            }
        }
    
    }
    

如您所见,我只是遍历每个属性,并用 SimpleValue-object 替换 UtilDateSimpleValue 来替换 java.util.Date 类型的属性。这是一个非常简单的类——它实现了与 SimpleValue 对象相同的接口(interface),例如 org.hibernate.mapping.KeyValue。在构造函数中传递了原始 SimpleValue 对象 - 因此每次对 UtilDateSimpleValue 的调用都被重定向到原始对象,但有一个异常(exception) - 方法 getType(...) 返回我的自定义类型。

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;
    }
    ...
}
  • 最后一步是 UtilDateUserType 的实现。我只是扩展原始的 org.hibernate.type.TimestampType 并像这样覆盖它的方法 get():

    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;
        }
    }
    

就是这样。有点棘手,但现在每个 java.util.Date 属性都作为 java.util.Date 返回,而无需对现有代码进行任何额外修改(注释或修改 setter )。正如我在 Hibernate 4 或更高版本中发现的那样,有一种更简单的方法可以替换您自己的类型(请参阅此处的详细信息:Hibernate TypeResolver)。欢迎任何建议或批评。

关于java - 如何强制 Hibernate 将日期返回为 java.util.Date 而不是 Timestamp?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9533935/

相关文章:

java - 无需登录即可直接从 Alfresco 访问文件/内容

java - 同步写入两个集合

java - 使用 java 的 MVC - 返回 NULL

mysql - 我无法生成自动递增的 Id,并且 hibernate 始终生成 1 作为 Id

java - 使用日志工具记录 HQL 返回值时遇到问题

java - 复合标识符的任何部分都不能为空

java - 外键映射到 Embeddable 类

java - JPA - 跨 EntityManager 获取更新/同步的实体(刷新)

java - 在 persistence.xml 中外部化动态属性

java - 使用 hibernate 取消映射的复合对象