如何使用java.sql.Timestamp作为真正的java.util.Date与JPA

时间:2010-10-25 12:00:36

标签: java datetime jpa persistence

我有一个关于日期管理的问题,以毫秒为单位。我理解需要使用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获得结果?

你有没有遇到过这个问题?我不正确的是什么?处理这个问题的最佳方法是什么?

5 个答案:

答案 0 :(得分:12)

TBH,我不确定这个的确切状态,但Hibernate(这是你的JPA提供者,对吧?)处理TIMESTAMP列的方式确实存在问题。

要将SQL TIMESTAMP映射到java.util.Date,Hibernate会使用TimestampTypejava.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 threadthis one(阅读所有页面)中,似乎Hibernate用户和开发人员从未就此问题达成一致(请参阅HB-681等问题),我只是不明白为什么。< / p>

也许只是我,也许我只是为了其他人而错过了一些简单的东西,但问题对我来说显而易见,虽然我认为这个愚蠢的java.sql.Timestamp是罪魁祸首,我仍然认为Hibernate应该保护用户不受此影响问题。我不明白为什么加文不同意这一点。

我的建议是创建一个展示问题的测试用例(应该非常简单)并再次报告问题,看看你是否从当前团队那里得到更多积极反馈。

与此同时,您可以使用自定义类型自行“修复”问题,使用类似的内容(从论坛中获取并按原样粘贴):

{{1}}

答案 1 :(得分:7)

java.sql.Timestamp会覆盖compareTo(Date)方法,因此使用compareTo(..)

应该没问题

简而言之 - java.util.Datejava.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()值来比较时间值。

但是进行测试很痛苦,所以我实现了自己的类型处理程序

http://pastebin.com/7TgtEd3x

http://pastebin.com/DMrxzUEV

我已经覆盖了我使用的方言:

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参考实现中尝试过这个?