我有一个关于日期管理的问题,以毫秒为单位。我理解需要使用TIMESTAMP来存储毫秒:
@Temporal(TIMESTAMP)
@Column(name="DATE_COLUMN", nullable = false)
@Override public java.util.Date getDate() { return this.date; }
但是如果我不能将这个日期与java.util.Date的另一个实例进行比较,除非我注意equals()调用的顺序,因为this.date
实例是java.sql.Timestamp。如何从JPA获取java.util.Date?因为来自JPA的日期,即使方法签名是java.util.Date实际上也是java.sql.Timestamp的实例。
java.util.Date newDate = new Date(this.date.getTime());
this.date.equals(newDate) == false
newDate.equals(this.date) == true
我尝试在持久化类中修改我的方法:
@Override
public Date getDate() {
return this.date == null ? null : new Date(this.date.getTime());
}
它正在运行,但是大量数据效率不高。
还有其他选择:
我可以使用@PostLoad
修改持久化类的设计,以便在检索之后从持续日期创建java.util.Date。
我想知道我是否无法使用ClassTransformer
获得结果?
你有没有遇到过这个问题?我不正确的是什么?处理这个问题的最佳方法是什么?
答案 0 :(得分:12)
TBH,我不确定这个的确切状态,但Hibernate(这是你的JPA提供者,对吧?)处理TIMESTAMP
列的方式确实存在问题。
要将SQL TIMESTAMP
映射到java.util.Date
,Hibernate会使用TimestampType
,java.util.Date
实际上会为您的Timestamp.equals(Object)
属性分配java.sql.Timestamp
。虽然这是“合法的”,但问题是Date.equals(Object)
不对称(为什么在地球上?!)这会破坏myDate.equals(someRealJavaUtilDate)
的语义。
因此,如果myDate
映射到SQL TIMESTAMP
,则不能“盲目地”使用public class TimeMillisType extends org.hibernate.type.TimestampType {
public Date get(ResultSet rs, String name) throws SQLException {
Timestamp timestamp = rs.getTimestamp(name);
if (timestamp == null) return null;
return
new Date(timestamp.getTime()+timestamp.getNanos()/1000000);
}
}
,这当然不是真的可以接受。
但是,尽管在Hibernate论坛上对此进行了广泛讨论,例如在this thread和this one(阅读所有页面)中,似乎Hibernate用户和开发人员从未就此问题达成一致(请参阅HB-681等问题),我只是不明白为什么。< / p>
也许只是我,也许我只是为了其他人而错过了一些简单的东西,但问题对我来说显而易见,虽然我认为这个愚蠢的java.sql.Timestamp
是罪魁祸首,我仍然认为Hibernate应该保护用户不受此影响问题。我不明白为什么加文不同意这一点。
我的建议是创建一个展示问题的测试用例(应该非常简单)并再次报告问题,看看你是否从当前团队那里得到更多积极反馈。
与此同时,您可以使用自定义类型自行“修复”问题,使用类似的内容(从论坛中获取并按原样粘贴):
{{1}}
答案 1 :(得分:7)
java.sql.Timestamp
会覆盖compareTo(Date)
方法,因此使用compareTo(..)
简而言之 - java.util.Date
和java.sql.Timestamp
相互比较。
此外,您始终可以比较date.getTime()
,而不是对象本身。
更进一步 - 您可以使用long
字段来存储日期。甚至是DateTime
(来自joda-time)
答案 2 :(得分:4)
根据我的经验,你不希望java.sql.Timestamp进入你的逻辑 - 它会像你指出的那样产生许多奇怪的错误,如果你的应用程序进行序列化,它就不会变得更好。
如果它适用于返回新java.util.Date的覆盖,那么去那个。或者甚至更好,去JodaTime。你会在网上找到很多例子。我不担心这里的性能,因为你的数据库比创建一个新的java.util.Date对象要慢得多。
编辑: 我看到你正在使用Hibernate。如果您使用注释,您可以这样做:
@Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime getProvisionByTime() {
return provisionByTime;
}
然后你将在持久对象中从Jodatime获得漂亮的DateTime对象。如果您只想拥有日期,可以像这样使用LocalDate:
@Type(type = "org.joda.time.contrib.hibernate.PersistentLocalDate")
public LocalDate getCloudExpireDate() {
return cloudExpireDate;
}
如果您使用maven,以下依赖项应该为您设置(您可能需要更新hibernate版本)
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.6.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.3.1.GA</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time-hibernate</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6.1</version>
</dependency>
答案 3 :(得分:2)
这个问题对于DAO测试至关重要:
Employer employer1 = new Employer();
employer1.setName("namenamenamenamenamename");
employer1.setRegistered(new Date(1111111111)); // <- Date field
entityManager.persist(employer1);
assertNotNull(employer1.getId());
entityManager.flush();
entityManager.clear();
Employer employer2 = entityManager.find(Employer.class, employer1.getId());
assertNotNull(employer2);
assertEquals(employer1, employer2); // <- works
assertEquals(employer2, employer1); // <- fails !!!
因此结果非常令人惊讶,编写测试变得棘手。
但是在真实的业务逻辑中,你永远不会将实体用作set / map键,因为它很大并且是可变的。并且您永远不会通过equal
比较比较时间值。并且应该避免比较整个实体。
通常的场景使用不可变的实体ID作为map / set键,并使用compareTo()
方法或仅使用getTime()
值来比较时间值。
但是进行测试很痛苦,所以我实现了自己的类型处理程序
我已经覆盖了我使用的方言:
package xxx;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.type.AdaptedImmutableType;
import xxx.DateTimestampType;
import java.util.Date;
public class CustomHSQLDialect extends HSQLDialect {
public CustomHSQLDialect() {
addTypeOverride(DateTimestampType.INSTANCE);
addTypeOverride(new AdaptedImmutableType<Date>(DateTimestampType.INSTANCE));
}
}
我还没有决定 - 我会将这种方法用于测试和生产,还是仅用于测试。
答案 4 :(得分:1)
JPA应该为java.util.Date类型的属性返回java.util.Date,@ Temporal(TIMESTAMP)注释应该只影响日期的存储方式。你不应该得到一个java.sql.Timestamp。
您使用的是哪个JPA提供商?你是否在EclipseLink,JPA参考实现中尝试过这个?