Рубрики
Без рубрики

Как сопоставить данные и метку времени с помощью JPA и гибернации

Узнайте, как лучше всего сопоставлять типы столбцов даты, метки времени или времени при использовании JPA и гибернации с использованием данных, локальных данных и локального времени.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье я покажу вам, как лучше всего сопоставлять типы столбцов даты и метки времени при использовании JPA и Hibernate.

Хотя существует множество вариантов отображения столбцов даты и метки времени на стороне Java, как вы скоро увидите, не все из них подходят.

Столбцы базы данных с датами и метками времени

Предположим, что у нас есть таблица user_account , в которой хранится дата подписки пользователя, и таблица post со столбцом published_on , в котором хранится значение метки времени, когда сообщение было опубликовано.

Столбцы даты и времени MySQL

Если мы используем MySQL, тип столбца subscribed_on в таблице user_account может быть ДАТА , и тип столбца published_on в таблице post может быть ДАТА-ВРЕМЯ .

MySQL также предлагает МЕТКУ ВРЕМЕНИ столбец для хранения информации о дате и времени. Однако, поскольку максимальное значение столбца МЕТКА ВРЕМЕНИ равно 2038-01-09 03:14:07 , обычно лучше использовать DATETIME вместо этого.

PostgreSQL Столбцы даты и метки времени

Если мы используем PostgreSQL, тип столбца subscribed_on в таблице user_account может быть ДАТА и тип столбца published_on в таблице post могут быть ОТМЕТКОЙ ВРЕМЕНИ .

PostgreSQL также предлагает ВРЕМЕННУЮ МЕТКУ С ЧАСОВЫМ ПОЯСОМ столбец для хранения информации о дате и времени. Столбец МЕТКА ВРЕМЕНИ С ЧАСОВЫМ ПОЯСОМ преобразует указанное значение метки времени в UTC на основе текущего ЧАСОВОЙ ПОЯС настройка.

Далее мы увидим, какие у нас есть опции для сопоставления типов столбцов даты и метки времени в качестве атрибутов сущности JPA или Hibernate.

Сохранение метки времени в формате UTC

Как я объяснил в этой статье , очень хорошая идея хранить значения меток времени в формате UTC.

Во-первых, вам необходимо настроить сервер базы данных для использования часового пояса UTC. Например, в PostgreSQL вы можете сделать это, указав следующую настройку в файле postgresql.conf :

timezone = 'UTC'

В MySQL вы можете установить это в my.cnf (например, Linux) или my.ini (например, файлы конфигурации Windows):

default_time_zone='+00:00'

Для MySQL 8 убедитесь, что вы используете драйвер 8.0.20 Connector/J или новее, так как он исправляет Ошибка преобразования ДАТЫ .

Во-вторых, вам нужно установить hibernate.jdbc.time_zone Свойство Hibernate имеет значение UTC .

Это можно сделать с помощью persistence.xml файл конфигурации:


Или через файл Spring Boot application.properties :

spring.jpa.properties.hibernate.jdbc.time_zone=UTC

Без предоставления этого свойства драйвер JDBC может преобразовать предоставленные значения временных меток из часового пояса JVM в часовой пояс базы данных.

Хранение столбцов данных и меток времени с помощью JPA и гибернации

Чтобы отобразить столбец даты, у нас есть следующие опции:

  • java.sql. Дата
  • java.util. Дата
  • ЛокалЬная дата

Чтобы отобразить столбец метки времени, мы можем использовать один из следующих типов Java:

  • java.sql. Отметка времени
  • java.util. Дата
  • Местное время
  • Смещение времени
  • Дата в зоне

Далее мы проанализируем все эти варианты и рассмотрим преимущества и недостатки сопоставления атрибутов каждой сущности.

Сопоставление даты и метки времени с помощью java.sql. Дата и java.sql. Отметка времени

JDBC предлагает java.sql. Дата и java.sql. Метка времени для сопоставления данных и столбцов меток времени, поэтому мы можем сопоставить subscribed_on и published_on столбцы, использующие следующие сопоставления объектов JPA и Hibernate:

@Column(name = "subscribed_on")
private java.sql.Date subscribedOn;
		
@Column(name = "published_on")
private java.sql.Timestamp publishedOn;

Учитывая, что у нас есть следующие полезные методы:

private final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

private final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

private java.sql.Date parseDate(String date) {
    try {
        return new Date(DATE_FORMAT.parse(date).getTime());
    } catch (ParseException e) {
        throw new IllegalArgumentException(e);
    }
}

private java.sql.Timestamp parseTimestamp(String timestamp) {
    try {
        return new Timestamp(DATE_TIME_FORMAT.parse(timestamp).getTime());
    } catch (ParseException e) {
        throw new IllegalArgumentException(e);
    }
}

При сохранении следующей Учетной записи пользователя и Должность организации:

UserAccount user = new UserAccount()
    .setId(1L)
    .setFirstName("Vlad")
    .setLastName("Mihalcea")
    .setSubscribedOn(
        parseDate("2013-09-29")
    );

Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence")
    .setCreatedBy(user)
    .setPublishedOn(
        parseTimestamp("2020-05-01 12:30:00")
    );

entityManager.persist(user);

entityManager.persist(post);

Hibernate генерирует соответствующие инструкции SQL INSERT:

INSERT INTO user_account (
    first_name, 
    last_name, 
    subscribed_on, 
    id
) 
VALUES (
    'Vlad', 
    'Mihalcea', 
    '2013-09-29', 
    1
)

INSERT INTO post (
    user_account_id, 
    published_on, 
    title, 
    id
) 
VALUES (
    1, 
    '2020-05-01 12:30:00', 
    'High-Performance Java Persistence', 
    1
)

И, при извлечении всего из базы данных, мы можем видеть, что Дата и Метка времени значения-это именно те, которые мы сохранили:

Post post = entityManager.find(
    Post.class, 1L
);

assertEquals(
    parseTimestamp("2020-05-01 12:30:00"),
    post.getPublishedOn()
);

UserAccount userAccount = post.getCreatedBy();

assertEquals(
    parseDate("2013-09-29"),
    userAccount.getSubscribedOn()
);

Хотя сопоставление является простым, большинство приложений не хотят привязывать свои сущности JPA к классам API JDBC. Итак, давайте посмотрим, какие у нас есть другие варианты.

Сопоставление даты и метки времени с помощью java.util. Дата

Вместо использования JDBC Дата и Метка времени классы, мы можем использовать java.util. Дата для обоих Дата и Время типы столбцов. Чтобы различать эти два типа столбцов, JPA предлагает аннотацию @Temporal , как показано на следующем сопоставлении атрибутов сущности JPA:

@Column(name = "subscribed_on")
@Temporal(TemporalType.DATE)
private java.util.Date subscribedOn;

@Column(name = "published_on")
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date publishedOn;

Утилита Парседат и parseTimestamp методы на этот раз не требуют дополнительной упаковки:

private final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

private final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

private java.util.Date parseDate(String date) {
    try {
        return DATE_FORMAT.parse(date);
    } catch (ParseException e) {
        throw new IllegalArgumentException(e);
    }
}

private java.util.Date parseTimestamp(String timestamp) {
    try {
        return DATE_TIME_FORMAT.parse(timestamp);
    } catch (ParseException e) {
        throw new IllegalArgumentException(e);
    }
}

Сохранение и извлечение Учетной записи пользователя и Сообщение сущности не меняются, поэтому оно не будет повторяться для краткости.

Преимущество использования java.util. Дата заключается в том, что сущности уровня доступа к данным больше не связаны с классами API JDBC. Недостатком является то, что нам необходимо предоставить аннотацию @Temporal , чтобы сообщить поставщику JPA о соответствующем типе столбца базы данных.

Сопоставление даты и метки времени с помощью LocalDate и LocalDateTime

Как объясняется в этой статье , JPA 2.2 добавляет поддержку API даты и времени Java 8.

Таким образом, мы можем сопоставить subscribed_on с Локальной датой и столбец published_on в Местное время So, we can map the subscribed_on

@Column(name = "subscribed_on")
private LocalDate subscribedOn;

@Column(name = "published_on")
private LocalDateTime publishedOn;

По сравнению с java.util. Date или его аналоги JDBC, API Java Date/Time намного проще в использовании и не требует никаких утилитарных методов, так как предоставляемые заводские методы очень просты в использовании:

UserAccount user = new UserAccount()
    .setId(1L)
    .setFirstName("Vlad")
    .setLastName("Mihalcea")
    .setSubscribedOn(
        LocalDate.of(
            2013, 9, 29
        )
    );

Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence")
    .setCreatedBy(user)
    .setPublishedOn(
        LocalDateTime.of(
            2020, 5, 1,
            12, 30, 0
        )
    );

entityManager.persist(user);

entityManager.persist(post);

Запросы SQL-вставки идентичны тем, которые были представлены ранее. И сущности также правильно извлекаются из базы данных:

Post post = entityManager.find(
    Post.class, 1L
);

assertEquals(
    LocalDateTime.of(
        2020, 5, 1,
        12, 30, 0
    ),
    post.getPublishedOn()
);

UserAccount userAccount = post.getCreatedBy();

assertEquals(
    LocalDate.of(
        2013, 9, 29
    ),
    userAccount.getSubscribedOn()
);

Сопоставление метки времени с использованием OffsetDateTime

Вы также можете использовать Java 8 OffsetDateTime для отображения столбца published_on :

@Column(name = "published_on")
private OffsetDateTime publishedOn;

Однако при сохранении объекта Post :

Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence")
    .setCreatedBy(user)
    .setPublishedOn(
        LocalDateTime.of(
            2020, 5, 1,
            12, 30, 0
        ).atOffset(ZoneOffset.UTC)
    );

entityManager.persist(post);

Мы видим, что Hibernate преобразовал метку времени в соответствии с нашим местным часовым поясом:

INSERT INTO post (
    user_account_id, 
    published_on, 
    title, 
    id
) 
VALUES (
    1, 
    '2020-05-01 15:30:00.0', 
    'High-Performance Java Persistence', 
    1
)

Это связано с тем, что дескриптор Java OffsetDateTime обертывает предоставленный Смещение времени в Временная метка , вот так:

return (X) Timestamp.from( offsetDateTime.toInstant() );

И при чтении его из базы данных он преобразует его в местный часовой пояс:

return OffsetDateTime.ofInstant( ts.toInstant(), ZoneId.systemDefault() );

Таким образом, это работает только в том случае, если используется системный часовой пояс:

Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence")
    .setCreatedBy(user)
    .setPublishedOn(
        LocalDateTime.of(
            2020, 5, 1,
            12, 30, 0
        ).atOffset(
            ZoneOffset.systemDefault()
                .getRules()
                .getOffset(LocalDateTime.now())
        )
    );

При сохранении атрибута OffsetDateTime сущности Hibernate не сохраняет смещение отдельно, поэтому используется локальное смещение. По этой причине OffsetDateTime не очень полезен, и его можно заменить на LocalDateTime вместо этого.

Если вам понравилась эта статья, держу пари, вам также понравятся моя Книга и Видеокурсы.

Отображение метки времени с помощью ZonedDateTime

Вы также можете использовать Java 8 ZonedDateTime для отображения столбца published_on :

@Column(name = "published_on")
private ZonedDateTime publishedOn;

Однако, точно так же, как это было в случае Смещение времени , только системный часовой пояс работает при сохранении и извлечении атрибутов сущности.

Итак, вот как вам нужно сохранить Дата в зоне So, this is how you need to persist the ZonedDateTime

Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence")
    .setCreatedBy(user)
    .setPublishedOn(
        LocalDateTime.of(
            2020, 5, 1,
            12, 30, 0
        ).atZone(ZoneId.systemDefault())
    );

Для того, чтобы иметь возможность извлечь его из базы данных:

assertEquals(
    LocalDateTime.of(
        2020, 5, 1,
        12, 30, 0
    ).atZone(ZoneId.systemDefault()),
    post.getPublishedOn()
);

При сохранении атрибута ZonedDateTime сущности Hibernate не сохраняет часовой пояс отдельно, поэтому используется локальное смещение. По этой причине ZonedDateTime не очень полезен, и его можно заменить на LocalDateTime вместо этого.

Вывод

При использовании JPA и гибернации java.util. Дата , а также Местное состояние и Местное время являются лучшими вариантами отображения столбцов данных и меток времени.