2012-03-02 107 views
47

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

我有一个持久化类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 
) 

然后保存我的时期对象到DB:

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

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

问题:是否有任何手段强迫Hibernate作为java.util.Date类型,而不是时间戳的对象返回日期没有每一个这样的变量的显式的修改(例如,它必须能够刚刚工作,没有明确修改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. 通过修改制定者(深层副本) - 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/

+6

我认为你应该使用刚刚显示的明确答案之一。主要用于未来的代码维护。它将允许新开发人员准确地看到你在做什么,而不是花费大量时间来弄清楚为什么系统中的日期与其他所有休眠系统的工作方式不同。 – Zoidberg 2012-03-02 13:23:41

+0

第二种解决方案最适合我。另外,从你的blogpost看来,你依靠hibernate来创建你的数据库结构。如果你把它放在你的控制范围内,并且只是使用'date'而不是'timestamp'到处? – 2012-03-02 14:59:11

+1

+1这确实是一个令人讨厌的问题。我现在与Hibernate一起工作了很多年,并且我不知道任何有关此的官方Hibernate文档。我正在使用*修改的setter *方法。我也在getter中做了一个深层拷贝,因为'java.util.Date'不是不可变的,所以无论如何深度拷贝应该是首选。 – tscho 2012-03-02 21:45:29

回答

9

所以,我花了一些时间与这个问题,并找到了解决办法。这不是很漂亮,但至少有一个起点 - 也许有人会用一些有用的评论来补充这一点。

有关映射的一些信息,我在过程中发现:包含休眠类型的基本映射属性类型

  • 类是org.hibernate.type.TypeFactory。所有这些映射存储在不可修改的映射

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

你可以用Hibernate类型org.hibernate.type.TimestampType

  • 下一个有趣的时刻assosited java.util.Date类型看 - 创建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配置对象,修改值ojects为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); 
           } 
          } 
         } 
        } 
    
    } 
    

正如你可以看到我只是UtilDateSimpleValue每个属性和替代SimpleValue对象迭代为java.util.Date类型的属性。这是非常简单的类 - 它实现与SimpleValue对象相同的接口,例如org.hibernate.mapping.KeyValue。在构造函数中传递原始的SimpleValue对象 - 因此,对UtilDateSimpleValue的每次调用都被重定向到原始对象,但有一个例外 - 方法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的形式返回,而不需要对现有代码(注释或修改设置器)进行任何额外的修改。正如我在Hibernate 4或更高版本中发现的那样,有一种更简单的方法可以替代您自己的类型(请参阅此处的详细信息:Hibernate TypeResolver)。任何建议或批评都是受欢迎的。

5

接近1和2显然不工作,因为你得到java.sql.Date对象,每JPA/Hibernate的规范,而不是java.util.Date。从方法3和方法4,我宁愿选择后者,因为它更具说明性,并且可以同时使用字段和getter注释。

您已经在引用的博客文章中列出了解决方案4,因为@ tscho非常乐意指出。也许defaultForType(见下文)应该给你集中解决方案你正在寻找。当然,仍然需要区分日期(没有时间)和时间戳字段。

以供将来参考,我会离开使用自己的Hibernate UserType这里的总结:

为了使休眠给你java.util.Date情况下,你可以使用@Type@TypeDef注解来定义你的java.util中的不同映射.Date来自数据库的Java类型。

请参阅核心参考手册here中的示例。

  1. 执行UserType来完成实际的管道(转换为java.util.Date或从java.util.Date转换),例如, TimestampAsJavaUtilDateType
  2. 在一个实体或package-info.java上添加一个@TypeDef注释 - 对于会话工厂全局都可用(请参见上面的手动链接)。您可以使用defaultForType在类型java.util.Date的所有映射字段上应用类型转换。

    @TypeDef 
        name = "timestampAsJavaUtilDate", 
        defaultForType = java.util.Date.class, /* applied globally */ 
        typeClass = TimestampAsJavaUtilDateType.class 
    ) 
    
  3. 可选,而不是defaultForType,可以单独使用@Type注解你的领域/干将:

    @Entity 
    public class MyEntity { 
        [...] 
        @Type(type="timestampAsJavaUtilDate") 
        private java.util.Date myDate; 
        [...] 
    } 
    

附:建议一种完全不同的方法:我们通常不会比较使用equals()的Date对象。相反,我们使用实用程序类和方法来比较只有两个Date实例(或另一个分辨率,如秒)的日历日期,而不考虑确切的实现类型。这对我们运作良好。

+1

OP在可能的解决方案的第4点中提出了这种方法。 [链接的博客文章](http://blogs.sourceallies。com/2012/02/hibernate-date-vs-timestamp /)也提到了这种方法的微妙副作用:从java.sql.Timestamp到java.util.Date的转换降低了纳秒精度。这对于修改的setter方法也是如此。这就是为什么我还要以毫秒的精度定义数据库中的时间戳列。 – tscho 2012-03-03 12:45:40

+1

@tscho你说得对,我完全错过了博客文章。我将编辑我的答案来反映这一点,但我仍然认为它很有用,因为它在这里是在stackoverflow而不是外部博客。 – 2012-03-03 17:40:48

1

Java平台库中有一些类可以扩展可实例化的 类并添加一个值组件。例如,java.sql.Timestamp 扩展了java.util.Date并添加了一个纳秒字段。 Timestamp的等号实现 确实违反了对称性,并且如果 时间戳和日期对象在同一个集合中使用或以其他方式混合,则会导致不稳定的行为。 Timestamp类有一个免责声明,告诫程序员对 混合日期和时间戳。尽管只要你将它们分开,你就不会遇到麻烦,但没有什么可以阻止你将它们混合在一起,并且产生的错误可能很难调试。 Timestamp类的这种行为是一个 错误,不应该被仿真。

退房此链接

http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/

9

使用自定义UserType的简单替代方法是构造一个新的java.util。日期的制定者在你的持久bean中的日期属性,如:

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

} 
+0

public void getDate()'''不能返回Date类型的引用。 – Wrench 2017-02-15 16:53:34

0

我跑进这个是比较失败日期的问题,以及我的JUnit的assertEquals到Hibernate发出“java.util.Date”类型(如问题中所述,实际上是时间戳)。事实证明,通过将映射更改为'date'而不是'java.util.Date',Hibernate生成java.util.Date成员。我使用Hibernate 4.1.12版本的XML映射文件。

这个版本发出 'java.util.Timestamp':

<property name="date" column="DAY" type="java.util.Date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" /> 

这个版本发出 'java.util.Date':

<property name="date" column="DAY" type="date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" /> 

但是请注意,如果休眠被用来产生DDL,那么这些将生成不同的SQL类型(日期'日期'和时间戳'java.util.Date')。

2

这里是Hibernate 4.3.7.Final的解决方案。

pacakge-info.java包含

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

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

该解决方案主要依靠TimestampType实现通过匿名类类型JdbcTimestampTypeDescriptor的添加额外的行为。

+0

工作很好! 但是在wrap方法中需要一个空检查。 – 2017-03-06 16:34:19

2

只需在实体类中为java.util.Date字段添加此注释@Temporal(TemporalType.DATE)即可。

更多可用信息在this stackoverflow答案。

相关问题