Как использовать java.sql.Timestamp как реальный java.util.Date с JPA
У меня проблема с управлением датами с миллисекундами. Я понимаю необходимость использования 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
Я пытаюсь изменить свой метод в классе persistence:
@Override
public Date getDate() {
return this.date == null ? null : new Date(this.date.getTime());
}
Он работает, но он не эффективен с большим количеством данных.
Существуют и другие варианты:
-
Я мог бы изменить дизайн моего класса persistence, используя @PostLoad
, чтобы создать java.util.Date из оставшейся даты после его получения.
-
Интересно, не могу ли я получить результат с помощью ClassTransformer
?
Вы когда-нибудь сталкивались с этой проблемой? Что я не правильно? Каков наилучший способ справиться с этой проблемой?
Ответы
Ответ 1
java.sql.Timestamp
переопределяет метод compareTo(Date)
, поэтому без проблем следует использовать compareTo(..)
Короче - java.util.Date
и java.sql.Timestamp
взаимно сопоставимы.
Кроме того, вы всегда можете сравнить date.getTime()
, а не сами объекты.
И еще дальше - вы можете использовать поле long
для хранения даты. Или даже a DateTime
(от joda-time)
Ответ 2
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);
}
}
Ответ 3
По моему опыту вы не хотите, чтобы 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, следующие настройки должны быть настроены для вас (вам может потребоваться обновить версии спящего режима)
<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>
Ответ 4
Проблема имеет решающее значение для тестов 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 !!!
Итак, результат действительно удивителен, и писать тесты стало сложно.
Но в реальной бизнес-логике вы никогда не будете использовать объект как ключ набора/карты, потому что он огромен и он изменен. И вы никогда не сравните значения времени при сравнении equal
. Кроме того, следует избегать сравнения целых объектов.
Обычный сценарий использует неизменяемый идентификатор объекта для ключа 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));
}
}
Я еще не решил - использовал бы этот подход как для тестов, так и для производства или только для тестов.
Ответ 5
JPA должна вернуть java.util.Date для атрибута типа java.util.Date, аннотация @Temporal (TIMESTAMP) должна влиять только на то, как дата сохраняется. Вам не нужно возвращать java.sql.Timestamp.
Какой поставщик JPA вы используете? Вы пробовали это в EclipseLink, эталонной реализации JPA?