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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4014303/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 08:25:30  来源:igfitidea点击:

How to use java.sql.Timestamp as real java.util.Date with JPA

javadatetimejpapersistence

提问by chepseskaf

I have a problem about the management of dates with milliseconds. I understand the need to use the TIMESTAMP to store milliseconds:

我有一个关于以毫秒为单位的日期管理的问题。我理解需要使用 TIMESTAMP 来存储毫秒:

@Temporal(TIMESTAMP)
@Column(name="DATE_COLUMN", nullable = false)
@Override public java.util.Date getDate() { return this.date; }

But if I can't compare this date to another instance of java.util.Date, unless I pay attention to the order of equals() call, because this.dateinstance is a java.sql.Timestamp. How to get a java.util.Date from JPA ? Because the date that comes from JPA, even if the method signature is a java.util.Date is actually an instance of java.sql.Timestamp.

但是如果我不能将这个日期与另一个 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

I've try to modify my method in the persistence class:

我尝试在持久性类中修改我的方法:

@Override
public Date getDate() {
  return this.date == null ? null : new Date(this.date.getTime());
}

It's working, but it's not efficient with lots of data.

它正在工作,但对于大量数据来说效率不高。

There are other options :

还有其他选择:

  • I could modify the design of my persistence class, using @PostLoadin order to create a java.util.Date from the persited date after I retrieve it.

  • I wonder if I can not get a result using a ClassTransformer?

  • 我可以修改我的持久性类的设计,使用@PostLoad以便在我检索它后从持久日期创建一个 java.util.Date。

  • 我想知道我是否无法使用ClassTransformer?

Have you ever been confronted with this problem? What I do not correctly? What is the best way to handle this problem?

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

采纳答案by Bozho

java.sql.Timestampoverrides the compareTo(Date)method, so it should be no problem using compareTo(..)

java.sql.Timestamp覆盖该compareTo(Date)方法,因此使用应该没问题compareTo(..)

In short - java.util.Dateand java.sql.Timestampare mutually comparable.

简而言之 -java.util.Date并且java.sql.Timestamp可以相互比较。

Furthermore, you can always compare the date.getTime(), rather than the objects themselves.

此外,您始终可以比较date.getTime(),而不是对象本身。

And even further - you can use a longfield to store the date. Or even a DateTime(from joda-time)

更进一步 - 您可以使用long字段来存储日期。甚至是DateTime(来自 joda-time)

回答by Knubo

In my experience you don't want the java.sql.Timestamp out into your logic - it creates a lot of strange errors just as you pointed out, and it does not get any better if your application does serialization.

根据我的经验,您不希望 java.sql.Timestamp 进入您的逻辑 - 正如您指出的那样,它会产生很多奇怪的错误,并且如果您的应用程序进行序列化,它不会变得更好。

If it works with the override that returns a new java.util.Date then go for that one. Or even better, go for JodaTime. You'll find lots of examples out on the net doing that. I would not worry about performance here as your database is in magnitude more slow than the creation of a new java.util.Date object.

如果它与返回一个新的 java.util.Date 的覆盖一起工作,那么去那个。或者更好的是,选择 JodaTime。你会在网上找到很多这样做的例子。我不会担心这里的性能,因为您的数据库比创建新的 java.util.Date 对象要慢得多。

EDIT: I see that you are using Hibernate. If you use annotations you can do:

编辑:我看到您正在使用 Hibernate。如果您使用注释,您可以执行以下操作:

@Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime getProvisionByTime() {
    return provisionByTime;
}

Then you will get nice DateTime objects from Jodatime in your persistent objects. If you want to only have a date, you can use LocalDate like this:

然后,您将在持久对象中从 Jodatime 中获得不错的 DateTime 对象。如果你只想有一个日期,你可以像这样使用 LocalDate:

@Type(type = "org.joda.time.contrib.hibernate.PersistentLocalDate")
public LocalDate getCloudExpireDate() {
    return cloudExpireDate;
}

IF you use maven, the following dependencies should get this set up for you (you might need to update the hibernate versions)

如果您使用 maven,以下依赖项应该为您设置好(您可能需要更新休眠版本)

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

回答by James

JPA should return a java.util.Date for an attribute of type java.util.Date, the @Temporal(TIMESTAMP) annotation should only affect how the date is stored. You should not get a java.sql.Timestamp back.

JPA 应该为 java.util.Date 类型的属性返回一个 java.util.Date,@Temporal(TIMESTAMP) 注释应该只影响日期的存储方式。你不应该得到 java.sql.Timestamp 回来。

What JPA provider are you using? Have you tried this in EclipseLink, the JPA reference implementation?

您使用的是什么 JPA 提供程序?您是否在 JPA 参考实现 EclipseLink 中尝试过这个?

回答by Pascal Thivent

TBH, I'm not sure of the exact status of this but there might indeed be a problem with the way Hibernate (which is your JPA provider, right?) handles TIMESTAMPcolumns.

TBH,我不确定它的确切状态,但 Hibernate(这是您的 JPA 提供程序,对吗?)处理TIMESTAMP列的方式确实可能存在问题。

To map a SQL TIMESTAMPto a java.util.Date, Hibernate uses the TimestampTypewhich will actually assign a java.sql.Timestampto your java.util.Dateattribute. And while this is "legal", the problem is that Timestamp.equals(Object)is not symmetric(why on earth?!) and this breaks the semantics of Date.equals(Object).

要将 SQL 映射 TIMESTAMP到 a java.util.Date,Hibernate 使用TimestampType实际上将 a 分配java.sql.Timestamp给您的java.util.Date属性。虽然这是“合法”的,问题是,Timestamp.equals(Object)不是对称的(为什么地球上?!),这断裂的语义Date.equals(Object)

As a consequence, you can't "blindly" use myDate.equals(someRealJavaUtilDate)if myDateis mapped to a SQL TIMESTAMP, which is of course not really acceptable.

因此,您不能“盲目”使用myDate.equals(someRealJavaUtilDate)ifmyDate映射到 SQL TIMESTAMP,这当然不是真正可以接受的。

But although this has been extensively discussed on the Hibernate forums, e.g. in this threadand this one(read all pages), it seems that Hibernate users and developers never agreed on the problem (see issues like HB-681) and I just don't understand why.

但是,尽管这已经在 Hibernate 论坛上进行了广泛的讨论,例如在这个线程这个(阅读所有页面)中,似乎 Hibernate 用户和开发人员从未就这个问题达成一致(参见HB-681 之类的问题),我只是不同意不明白为什么。

Maybe it's just me, maybe I just missing something simple for others, but the problem looks obvious to me and while I consider this stupid java.sql.Timestampto be the culprit, I still think that Hibernate should shield users from this issue. I don't understand why Gavin didn't agree on this.

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

My suggestion would be to create a test case demonstrating the issue (should be pretty simple) and to report the problem (again) to see if you get more positive feedback from the current team.

我的建议是创建一个测试用例来演示问题(应该很简单)并报告问题(再次)以查看您是否从当前团队获得更多积极反馈。

Meanwhile, you could use a custom type to "fix" the problem yourself, using something like this (taken from the forum and pasted as is):

同时,您可以使用自定义类型自己“修复”问题,使用类似的东西(取自论坛并按原样粘贴):

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

}

回答by Nick Mazurkin

The problem is critical for DAO tests:

该问题对于 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 !!!

So the result is really surprising and writing tests became tricky.

所以结果真的很令人惊讶,编写测试变得棘手。

But in the real business logic you will never use entity as a set/map key because it is huge and it is mutable. And you will never compare time values by equalcomparison. And comparing whole entities should be avoided too.

但是在实际的业务逻辑中,您永远不会将实体用作 set/map 键,因为它很大并且是可变的。而且您永远不会通过equal比较来比较时间值。并且也应该避免比较整个实体。

The usual scenario uses immutable entity ID for map/set key and compares time values with compareTo()method or just using getTime()values.

通常情况下,地图/设置键使用不可变的实体 ID,并将时间值与compareTo()方法或仅使用getTime()值进行比较。

But making tests is a pain so I implemented my own type handlers

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

http://pastebin.com/7TgtEd3x

http://pastebin.com/7TgtEd3x

http://pastebin.com/DMrxzUEV

http://pastebin.com/DMrxzUEV

And I have overridden the dialect I use:

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

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

I haven't decided yet - would I use this approach both for tests and production or for tests only.

我还没有决定 - 我是将这种方法用于测试和生产还是仅用于测试。