如何强制Hibernate返回date为java.util.Date而不是Timestamp?
情况 :
我有一个持久化的类与java.util.Datetypes的variables:
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);
之后,如果我试图从数据库中提取我的对象,它会返回variables的时间戳types的startDate – 所有我想要使用equals(…)的地方都返回false。
问题 :是否有任何手段迫使Hibernate返回date作为java.util.Datetypes的对象,而不是时间戳没有显式修改每个这样的variables(例如,它必须能够工作,没有显式修改java.util的已有variables.Datetypes)?
注意:
我发现一些明确的解决scheme,使用注释或setter被修改 – 但我有很多类与datevariables – 所以我需要一个集中的解决scheme,所有下面描述是不够的:
-
使用注解@Type: – java.sql.Date将被返回
@Column @Type(type="date") private Date startDate;
-
使用注释@Temporal(TemporalType.DATE) – java.sql.Date将被返回
@Temporal(TemporalType.DATE) @Column(name=”CREATION_DATE”) private Date startDate;
-
通过修改setter(深层复制) – java.util.Date将被返回
public void setStartDate(Date startDate) { if (startDate != null) { this.startDate = new Date(startDate.getTime()); } else { this.startDate = null; } }
-
通过创build我自己的types: – java.util.Date将被返回
详细信息在这里给出: http : //blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
所以,我花了一些时间来解决这个问题。 这不是一个漂亮的,但至less有一个起点 – 也许有人会补充一些有用的意见。
关于我在过程中发现的映射的一些信息:
-
包含Hibernatetypes到属性types的基本映射的类是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.Datetypes与Hibernatetypesorg.hibernate.type.TimestampType
-
下一个有趣的时刻 – 创buildHibernate org.hibernate.cfg.Configuration – 包含映射类的所有信息的对象。 这些类和它们的属性可以像这样被提取:
Iterator clsMappings = cfg.getClassMappings(); while(clsMappings.hasNext()){ PersistentClass mapping = (PersistentClass) clsMappings.next(); handleProperties(mapping.getPropertyIterator(), map); }
-
绝大多数属性是org.hibernate.mapping.SimpleValuetypes的对象。 我们感兴趣的是SimpleValue.getType()方法 – 在这个方法中定义了什么types将用于在使用数据库时来回转换属性值
Type result = TypeFactory.heuristicType(typeName, typeParameters);
在这一点上我明白,我无法修改BASIC_TYPES – 所以唯一的办法 – 将SimpleValue对象replace为我的自定义对象的java.util.Datetypes的属性,将能够知道要转换的确切types。
解决scheme:
-
通过扩展HibernatePersistence类并覆盖其方法createContainerEntityManagerFactory来创build自定义容器实体pipe理器工厂:
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); } } }
-
创buildHibernateconfiguration对象,修改java.util.Date属性的值,然后创build自定义实体pipe理器工厂。
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); } } } } }
正如你所看到的,我只是迭代每一个属性,并将java.util.Datetypes的属性replace为UtilDateSimpleValue的SimpleValue对象。 这是非常简单的类 – 它实现了与SimpleValue对象相同的接口,例如org.hibernate.mapping.KeyValue。 在构造函数原始SimpleValue对象被传递 – 所以每一个调用UtilDateSimpleValue被redirect到原始对象有一个例外 – 方法getType(…)返回我的自定义types。
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的forms返回,而不需要对现有代码(注释或修改设置器)进行任何额外的修改。 正如我在Hibernate 4或更高版本中发现的那样,有一种更简单的方法来replace你自己的types(参见这里的细节: Hibernate TypeResolver )。 任何build议或批评是受欢迎的。
使用自定义UserType的简单替代方法是在setter中为持久化bean中的date属性构造一个新的java.util.Date,例如:
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()); } }
方法1和方法2显然不起作用,因为您获得java.sql.Date
对象,每个JPA / Hibernate规范,而不是java.util.Date
。 从方法3和方法4,我宁愿select后者,因为它更具说明性,并且可以同时使用字段和getter注释。
您已经在引用的博客文章中列出了解决scheme4,因为@ tscho非常乐意指出。 也许defaultForType (见下文)应该给你你正在寻找的集中解决scheme 。 当然还是需要区分date(没有时间)和时间戳字段。
为了将来的参考,我将在这里留下使用你自己的Hibernate UserType的总结:
为了让Hibernate为您提供java.util.Date
实例,可以使用@Type和@TypeDef注释来定义java.util.Date Javatypes与数据库的不同映射。
请参阅核心参考手册中的示例。
- 实现一个UserType来完成实际的pipe道(从java.util.Date到/从java.util.Date的转换),例如
TimestampAsJavaUtilDateType
-
在一个实体或package-info.java上添加一个@TypeDef注释 – 这两个对会话工厂来说都是全局可用的(参见上面的手册链接)。 您可以使用defaultForType将types转换应用于
java.util.Date
types的所有映射字段。@TypeDef name = "timestampAsJavaUtilDate", defaultForType = java.util.Date.class, /* applied globally */ typeClass = TimestampAsJavaUtilDateType.class )
-
可选地,而不是
defaultForType
,你可以用@Type单独注释你的字段/获得者:@Entity public class MyEntity { [...] @Type(type="timestampAsJavaUtilDate") private java.util.Date myDate; [...] }
PS为了提出一个完全不同的方法:我们通常不会比较使用equals()的Date对象。 相反,我们使用一个工具类与方法来比较,例如只有两个Date实例的日历date(或另一个分辨率,如秒),无论确切的实现types。 那对我们很好。
这里是Hibernate 4.3.7.Final的解决scheme。
包含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); } }
此解决scheme主要依赖于TimestampType实现,通过types为JdbcTimestampTypeDescriptor的匿名类添加其他行为。
只需为实体类中的java.util.Date
字段添加此批注@Temporal(TemporalType.DATE)
。
更多信息在这个 stackoverflow答案。
Java平台库中有一些类可以扩展可实例化的类并添加一个值组件。 例如,java.sql.Timestamp扩展了java.util.Date并添加了一个纳秒字段。 Timestamp的等价实现确实违反了对称性,并且如果时间戳和date对象在同一个集合中使用或以其他方式混合,则会导致不稳定的行为。 Timestamp类有一个免责声明,告诫程序员不要混合date和时间戳。 虽然只要将它们分开就不会遇到麻烦,但没有什么可以阻止您将它们混合在一起,并且由此产生的错误可能很难debugging。 Timestamp类的这种行为是一个错误,不应该被仿效。
看看这个链接
http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
我遇到了这个问题,以及我的JUnit assertEquals失败,比较date到Hibernate发射'java.util.Date'types(如问题中所描述的实际上是时间戳)。 事实certificate,通过将映射更改为'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" />
但是,请注意,如果使用Hibernate生成DDL,那么这些将生成不同的SQLtypes(date'Date'和时间戳'java.util.Date')。