Как использовать java.sql.Timestamp в качестве реального java.util.Date с JPA - PullRequest
14 голосов
/ 25 октября 2010

У меня проблема с управлением датами в миллисекундах. Я понимаю необходимость использовать 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. Как получить java.util.Date от JPA? Поскольку дата, полученная из 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 ]

12 голосов
/ 26 октября 2010

TBH, я не уверен в точном статусе этого, но действительно может быть проблема с тем, как Hibernate (который является вашим JPA-провайдером, верно?) Обрабатывает TIMESTAMP столбцы.

Чтобы сопоставить SQL TIMESTAMP с java.util.Date, Hibernate использует TimestampType, который фактически назначит java.sql.Timestamp вашему атрибуту java.util.Date. И хотя это «законно», проблема в том, что Timestamp.equals(Object) является не симметричным (почему на земле ?!), и это нарушает семантику Date.equals(Object).

Как следствие, вы не можете «вслепую» использовать myDate.equals(someRealJavaUtilDate), если myDate сопоставлен с SQL TIMESTAMP, что, конечно, не совсем приемлемо.

Но хотя это широко обсуждалось на форумах Hibernate, например, в этой теме и этой (читать все страницы) кажется, что пользователи и разработчики Hibernate никогда не соглашались с этой проблемой (см. такие вопросы, как HB-681 ) и я просто не понимаю, почему.

Может быть, это только я, может быть, я просто упускаю что-то простое для других, но проблема выглядит очевидной для меня, и хотя я считаю этого глупого java.sql.Timestamp виновником, я все же думаю, что Hibernate должен оградить пользователей от этой проблемы. Я не понимаю, почему Гэвин не согласился с этим.

Я бы предложил создать тестовый пример, демонстрирующий проблему (должен быть довольно простым), и сообщить о проблеме (снова), чтобы увидеть, получаете ли вы более положительные отзывы от текущей команды.

Между тем, вы можете использовать пользовательский тип, чтобы «решить» проблему самостоятельно, используя что-то вроде этого (взято с форума и вставлено как есть):

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

}
7 голосов
/ 25 октября 2010

java.sql.Timestamp переопределяет метод compareTo(Date), поэтому не должно возникнуть никаких проблем при использовании compareTo(..)

Короче - java.util.Date и java.sql.Timestamp взаимно сопоставимы.

Более того, вы всегда можете сравнить date.getTime(), а не сами объекты.

И даже дальше - вы можете использовать поле long для хранения даты.Или даже DateTime (из времени йода)

4 голосов
/ 25 октября 2010

По моему опыту, вы не хотите, чтобы java.sql.Timestamp включался в вашу логику - он создает много странных ошибок, как вы указали, и не становится лучше, если ваше приложение выполняет сериализацию.

Если он работает с переопределением, которое возвращает новый java.util.Date, тогда перейдите к этому. Или даже лучше, пойти на JodaTime. Вы найдете много примеров в сети, делающих это. Я бы не стал беспокоиться о производительности, поскольку ваша база данных по величине медленнее, чем создание нового объекта java.util.Date.

EDIT: Я вижу, что вы используете Hibernate. Если вы используете аннотации, вы можете сделать:

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

Тогда вы получите красивые объекты DateTime от Jodatime в ваших постоянных объектах. Если вы хотите иметь только дату, вы можете использовать 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>
2 голосов
/ 14 июля 2011

Проблема является критической для тестов 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.Кроме того, следует избегать сравнения целых объектов.

В обычном сценарии используется идентификатор неизменяемой сущности для ключа карты / набора и сравнение значений времени с помощью метода 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));
    }
}

Я еще не решил - буду ли я использовать этот подход как для испытаний, так и для производства или только для испытаний.

1 голос
/ 25 октября 2010

JPA должен возвращать java.util.Date для атрибута типа java.util.Date, аннотация @Temporal (TIMESTAMP) должна влиять только на то, как сохраняется дата.Вы не должны возвращать java.sql.Timestamp.

Каким JPA-провайдером вы пользуетесь?Вы пробовали это в EclipseLink, эталонной реализации JPA?

...