场合:
我有一个可变的类,其变量为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 - 并且我尝试使用的所有地方等于(...)都返回false。
问题:有没有办法强制Hibernate将日期作为java.util.Date类型的对象而不是Timestamp返回,而不对每个这样的变量进行显式修改(例如,它必须能够正常工作,没有显式修改java.util.Date类型的现有变量)?
注意:
我找到了许多显式解决方案,其中使用了注释或修改了setter - 但我有许多带有Date变量的类 - 所以我需要一些集中式解决方案,下面描述的所有内容都不够好:
使用注释@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;
}
}
创建我自己的类型: - 将返回java.util.Date
详情如下: http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
答案 0 :(得分:9)
所以,我花了一些时间来解决这个问题并找到了解决方案。它不是一个漂亮的,但至少是一个起点 - 也许有人会补充一些有用的评论。
有关我在流程中找到的映射的一些信息:
包含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属性的值ojects,然后创建自定义实体管理器工厂。
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-object替换为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返回,而不对现有代码(注释或修改setter)进行任何其他修改。正如我在Hibernate 4或更高版本中发现的那样,有一种更简单的方法来替换您自己的类型(请参阅此处的详细信息:Hibernate TypeResolver)。欢迎任何建议或批评。
答案 1 :(得分:9)
使用自定义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());
}
}
答案 2 :(得分:5)
方法1和2显然不起作用,因为根据JPA / Hibernate规范得到java.sql.Date
个对象,而不是java.util.Date
。从方法3和方法4开始,我宁愿选择后者,因为它更具说明性,并且可以同时使用字段和getter注释。
您已经在引用的博客文章中列出了解决方案4,因为@tscho非常友好地指出。也许 defaultForType (见下文)应该为您提供您正在寻找的集中式解决方案。当然,仍然需要区分日期(没有时间)和时间戳字段。
为了将来参考,我将在此处留下使用您自己的Hibernate UserType的摘要:
为了让Hibernate为您提供java.util.Date
个实例,您可以使用@Type和@TypeDef注释来定义java.util.Date java类型与数据库的不同映射
请参阅核心参考手册here中的示例。
TimestampAsJavaUtilDateType
在一个实体或package-info.java中添加@TypeDef注释 - 两者都将在会话工厂中全局可用(请参阅上面的手动链接)。您可以使用 defaultForType 在java.util.Date
类型的所有映射字段上应用类型转换。
@TypeDef
name = "timestampAsJavaUtilDate",
defaultForType = java.util.Date.class, /* applied globally */
typeClass = TimestampAsJavaUtilDateType.class
)
可选地,您可以单独使用@Type注释字段/ getter,而不是defaultForType
:
@Entity
public class MyEntity {
[...]
@Type(type="timestampAsJavaUtilDate")
private java.util.Date myDate;
[...]
}
P.S。建议一个完全不同的方法:我们通常只是不使用equals()来比较Date对象。相反,我们使用实用程序类与方法进行比较,例如只有两个Date实例的日历日期(或其他分辨率,如秒),无论确切的实现类型如何。这对我们来说效果很好。
答案 3 :(得分:3)
这是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类型的匿名类添加其他行为。
答案 4 :(得分:2)
Java平台库中有一些类可以扩展实例化 class并添加一个值组件。例如,java.sql.Timestamp 扩展java.util.Date并添加纳秒字段。等于实施 对于时间戳,确实违反了对称性,并且如果可能导致不稳定的行为 Timestamp和Date对象在同一个集合中使用,或以其他方式混合使用。 Timestamp类有一个免责声明警告程序员 混合日期和时间戳。虽然你不会遇到麻烦,只要你 让它们分开,没有什么可以阻止你混合它们,而且 产生的错误可能很难调试。 Timestamp类的这种行为是一个 错误,不应该被模仿。
查看此链接
http://blogs.sourceallies.com/2012/02/hibernate-date-vs-timestamp/
答案 5 :(得分:2)
只需为实体类中的@Temporal(TemporalType.DATE)
字段添加此注释java.util.Date
即可。
this stackoverflow答案中提供了更多信息。
答案 6 :(得分:0)
我遇到了这个问题以及我的JUnit assertEquals未能将Dates与发布的Hibernate进行比较&#39; java.util.Date&#39;类型(问题中描述的实际上是时间戳)。事实证明,通过将映射更改为&#39; date&#39;而不是&#39; java.util.Date&#39; Hibernate生成java.util.Date成员。我正在使用Hibernate版本4.1.12的XML映射文件。
此版本发出&#39; java.util.Timestamp&#39;:
<property name="date" column="DAY" type="java.util.Date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />
此版本发出&#39; java.util.Date&#39;:
<property name="date" column="DAY" type="date" unique-key="KONSTRAINT_DATE_IDX" unique="false" not-null="true" />
但是,请注意,如果使用Hibernate生成DDL,那么这些将生成不同的SQL类型(日期为&#39;日期&#39;和时间戳为&#39; java.util.Date&#39;)
答案 7 :(得分:0)
在 Hibernate 实体 Filed 上使用 @Type 注释来自定义您与 DB 对象和 java 对象的映射