Hibernate с Java 8 LocalDate & LocalDateTime в базе данных

Мое требование состоит в том, чтобы хранить все даты и даты в часовом поясе UTC в базе данных. Я использую Java 8 LocalDate & LocalDateTime в моих объектах Hibernate.

Это правильно, поскольку у LocalDate & LocalDateTime нет часового пояса, связанного с ними?

Если нет, должен ли я вернуться к использованию старого (или устаревшего?) Date & Timestamp?

Или я должен использовать Java 8 Instant? Если использовать Instant, будет ли возможность хранить только часть даты без времени?

База данных MySQL и SQL Server, и это приложение Spring Boot.

Ответ 1

Типы "Local…" специально не имеют понятия о часовом поясе. Таким образом, они не представляют момент на временной шкале. LocalDateTime представляет неопределенный диапазон возможных моментов, но не имеет реального значения до назначения смещения или часового пояса. Это означает применение ZoneId для получения ZonedDateTime.

Например, чтобы сказать, что Рождество в этом году начинается в первый момент 25 декабря, мы говорим:

LocalDateTime ldt = LocalDateTime.of( 2017 , 12 , 25 , 0 , 0 , 0 , 0 );

Но этот полуночный удар случается раньше на востоке, чем на западе.

Вот почему отдел логистики эльфов наметил маршрут Санты, начинающийся в Кирибати в Тихом океане, самом раннем часовом поясе в мире на 14 часов впереди UTC. После доставки они направляют Санту на запад в такие места, как Новая Зеландия, на полночь позже. Затем в Азию для их полуночи позже. Затем Индия и т.д. Через несколько часов достигают Европы в полночь, а еще несколько часов спустя - на восточном побережье Северной Америки в полночь. Все эти места испытывали один и тот же LocalDateTime в разные моменты, каждая доставка была представлена отдельным объектом ZonedDateTime.

Так...

  • Если вы хотите записать концепцию Рождества, начинающуюся после полуночи 25-го числа, используйте LocalDateTime и запишите в столбец базы данных тип TIMESTAMP WITHOUT TIME ZONE.
  • Если вы хотите записать точный момент каждой доставки, которую делает Санта, используйте ZonedDateTime и запишите в столбец базы данных тип TIMESTAMP WITH TIME ZONE.

Об этом втором пункте помните, что почти каждая система баз данных будет использовать информацию о зоне, чтобы установить дату и время в формате UTC и сохранить это значение UTC. Некоторые также сохраняют информацию о зоне, но некоторые, такие как Postgres, отбрасывают информацию о зоне после ее использования для настройки в UTC. Таким образом, "с часовым поясом" является чем-то неправильным, на самом деле означает "с уважением к часовому поясу". Если вы хотите запомнить эту исходную зону, вам может потребоваться сохранить ее название в отдельном столбце рядом с ней.

Другая причина использовать типы Local… - для будущих встреч. Политики любят часто менять свой часовой пояс своей юрисдикции. Они любят переходить на летнее время (DST). Люблю менять даты своих визитов DST. Они любят отказаться от принятия DST. Они любят переопределять свои часовые пояса, меняя границы. Они любят пересматривать свое смещение от UTC иногда на 15 минут. И они редко дают предварительное уведомление, делая такие изменения с всего лишь месяц или два предупреждения.

Таким образом, чтобы назначить медицинский осмотр на следующий год или через шесть месяцев, определение часового пояса не может быть предсказано. Поэтому, если вы хотите назначить встречу в 9:00, вам следует использовать LocalTime или LocalDateTime, записанные в столбце базы данных типа TIMESTAMP WITHOUT TIME ZONE. В противном случае назначение в 9 часов утра, если оно зонировано, где перенос DST откладывается, может отображаться как 8 часов утра или 10 часов утра.

При создании прогнозируемого расписания вы можете применить часовой пояс (ZoneId) к этим "локальным" (незонированным) значениям для создания объектов ZonedDateTime. Но не полагайтесь на тех, кто слишком далек во времени, когда политики могут разрушить их значение, изменив зону (зоны).

Совет. Эти частые изменения летнего времени и часовых поясов означают, что вы должны регулярно обновлять базу данных tzdata своего часового пояса. Существует tzdata в вашей операционной системе, вашей JVM и, возможно, в вашей системе баз данных, такой как Postgres. Все три должны часто обновляться. Иногда зоны меняются быстрее, чем запланированные циклы обновления этих продуктов, таких как Турция в прошлом году, решившая остаться на летнее время с уведомлением всего за несколько недель. Так что иногда вам может понадобиться обновить эти файлы tzdata вручную. Oracle предоставляет инструмент для обновления tzdata их реализаций Java.

Общая лучшая практика обработки точных моментов - отслеживать их в UTC. Применяйте часовой пояс только там, где это необходимо, например, при представлении пользователю, который ожидает увидеть значения в своем собственном приходском часовом поясе. В java.time класс Instant представляет момент на временной шкале. В UTC с разрешением наносекунд.

Instant instant = Instant.now() ;  // Current moment on the timeline in UTC.
ZonedDateTime zdt = instant.atZone( z ) ;  // Assign a time zone to view the same moment through the lens of a particular regions wall-clock time.
Instant instant = zdt.toInstant();  // revert back to UTC, stripping away the time zone. But still the same moment in the timeline.

Кстати, драйверы, соответствующие JDBC 4.2 и более поздним версиям, могут напрямую работать с типами java.time через:

  • PreparedStatement::setObject
  • ResultSet::getObject

Как ни странно, спецификация JDBC 4.2 не требует поддержки двух наиболее распространенных типов java.time: Instant & ZonedDateTime. Спецификация требует поддержки OffsetDateTime. Таким образом, вы можете легко конвертировать туда-сюда.

Избегайте старых устаревших типов данных, таких как java.util.Date и java.sql.Timestamp, когда это возможно. Они плохо спроектированы, запутаны и несовершенны.

Поймите, что все эти четыре являются моментом на временной шкале в UTC:

  • Современные
    • java.time.Instant
    • java.time.OffsetDateTime с назначенным смещением ZoneOffset.UTC
  • Наследие
    • java.util.Date
    • java.sql.Timestamp

Если вы хотите использовать значение только для даты без времени суток и без часового пояса, используйте java.time.LocalDate. Этот класс вытесняет java.sql.Date.

Что касается конкретных баз данных, имейте в виду, что стандарт SQL едва затрагивает тему типов даты и времени и их обработки. Кроме того, различные базы данных сильно различаются, и я действительно имею в виду, что они поддерживают функции даты и времени. У некоторых практически нет поддержки. Некоторые смешивают стандартные типы SQL с проприетарными типами, которые либо предшествуют стандартным типам, либо предназначены как альтернативы стандартным типам. Кроме того, драйверы JDBC отличаются по своему поведению маршалингом значений даты и времени в/из базы данных. Обязательно изучите документацию и практикуйтесь, практикуйтесь, практикуйтесь.

Table of date-time types in Java (both legacy and modern) and in standard SQL

Ответ 2

Или я должен использовать Java 8 Instant? Если вы используете Instant, будет ли возможность хранить только дату, без времени?

Мгновенное действие должно быть подходящим для большинства операций.

Добавьте hibernate-java8 в pom.xml, чтобы поддерживать API-интерфейс Java 8:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-java8</artifactId>
    <version>${version.hibernate}</version>
</dependency>

Затем вы можете использовать LocalDate или LocalDateTime или Instant для полей сущности Hibernate. Вам нужно удалить @Temporal(TemporalType.TIMESTAMP).

Мое требование - хранить все даты и даты в часовом поясе UTC в базы данных. Я использую Java 8 LocalDate и LocalDateTime в моем Объекты спящего режима.

Правильно ли это, что LocalDate и LocalDateTime не имеют часовой пояс связанные с ними?

Вы можете установить часовой пояс JVM по умолчанию где-то в конфигурационном коде:

@PostConstruct
void setUTCTimezone() {
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

Затем вы будете использовать время UTC в своем коде.

Чтобы использовать типы даты Java 8 в DTO, вам нужно добавить Jsr310JpaConverters:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

и

@EntityScan(basePackageClasses = { Application.class,    Jsr310JpaConverters.class })
SpringBootApplication
public class Application { … }

Дополнительные параметры:

Ответ 3

Часть нового API дат разбивает типы дат вверх. Правильный класс, который включает часовые пояса, - ZonedDateTime

// Get the current date and time
      ZonedDateTime date1 = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
      System.out.println("date1: " + date1);

      ZonedDateTime zonedDateTime = ZonedDateTime.now();
      System.out.println("Zoned Date Time: " + zonedDateTime);

      ZoneId id = ZoneId.of("Europe/Paris");
      System.out.println("ZoneId: " + id);

      ZoneId currentZone = ZoneId.systemDefault();
      System.out.println("CurrentZone: " + currentZone);

Печать

date1: 2007-12-03T10:15:30+05:00[Asia/Karachi]
Zoned Date Time: 2017-04-18T11:36:09.126-04:00[America/New_York]
ZoneId: Europe/Paris
CurrentZone: America/New_York