Сохранение java.sql.Timestamp в MySQL столбец datetime изменяет время - PullRequest
1 голос
/ 08 ноября 2019

Я работаю над Spring Boot REST API, который использует базу данных MySQL. Я пытаюсь сохранить дату (со временем), полученную из клиентского приложения "как есть", которая соответствует времени UTC. По сути, уже согласовано, что даты будут отправляться туда и обратно в UTC, и преобразование в соответствующий часовой пояс будет выполняться на клиенте, поэтому у меня уже есть время UTC в JSON, и я пытаюсь сохранить его в столбце DATETIME. в MySQL.

Я выбрал DATETIME, потому что из MySQL reference он говорит, что TIMESTAMP преобразует значения, а DATETIME - нет, а это именно то, что мне нужно. я создал все необходимые сущности для сопоставления моих таблиц и снова, из ссылки , я видел, что DATETIME сопоставляется с java.sql.Timestamp, поэтому я использовал его как тип.

IЯ использую JpaRepository и вызываю его метод save, чтобы сохранить мою сущность. Что происходит: когда я сохраняю даты в MySQL, они конвертируются в UTC (я полагаю, потому что я нахожусь в UTC + 1, а даты на один час раньше, чем я вставил).

Я прочитал несколько ответов по SO, и я попытался использовать следующие атрибуты в моей строке соединения jdbc (как предложено здесь ): noDatetimeStringSync=true, useLegacyDatetimeCode=false, sessionVariables=time_zone='-00:00', но ни один изони сработали.

Я не знаю, может быть, Spring что-то связывает с этим?

Это часть JSON, который я получаю в запросе:

{
 "dateFrom": "2019-12-01 16:00:00",
 "dateTo": "2019-12-01 16:30:00"
}

Моя сущность имеет следующие свойства:

import java.sql.Timestamp;
...
private Timestamp dateFrom;
private Timestamp dateTo;

Столбцы таблицы в db:

date_from   datetime
date_to     datetime

Внутри сущности есть нужные значения, когда я преобразую String в Timestamp и вызываю метод save (). Так что что-то между JpaRepository и самой БД смешивается с датами. ОБНОВЛЕНИЕ: Отметка времени на самом деле была неправильным значением, она добавила ZoneInfo with ID="Europe/Prague" и zoneOffset=3600000 при преобразовании из строки.

В результате я получаю:2019-12-01 15:00:00 и 2019-12-01 15:30:00 в дБ.

То, что я хотел бы хранить эти даты точно так, как я их получил. Я даже думал о переходе на VARCHAR, потому что я так расстроен, но я не хочу этого делать, потому что мне нужно выполнять запросы, основанные на датах и ​​т. Д.

Есть ли способ, которым я могу достичь того, что янаходясь в поиске? Сначала это казалось довольно простым, но теперь это буквально сводит меня с ума.

[Дополнительная информация, если требуется] Я использую:

  • MySQL 5.7.27
  • Spring Boot (стартер-родитель) 2.2.0.RELEASE
  • mysql-connector-java

РЕДАКТИРОВАТЬ:

Возможно, я поступаю неправильно, поэтому, если бы вы могли подсказать, как я могу сделать это по-другому, это было бы здорово. Дело в том: мое приложение должно работать для пользователей в разных часовых поясах, и они должны общаться друг с другом через даты. Поэтому, если один пользователь в часовом поясе Европа / Прага говорит «давайте поговорим завтра в 17:00» с другим пользователем в Америке / Чикаго, мне нужно сохранить эту информацию таким образом, чтобы ее можно было перевести для пользователя в Америке по местному времени (но также и длялюбой другой пользователь в любом другом часовом поясе). Вот почему я выбрал хранение дат в формате UTC, а затем преобразовал их на стороне клиента в местное время пользователя. Но, видимо, я не совсем понимаю, как все это работает.

Ответы [ 3 ]

2 голосов
/ 12 ноября 2019

Благодаря deHaar и всем остальным, кто внес свой вклад в комментарии, я наконец заставил его работать так, как я планировал, поэтому я собираюсь опубликовать краткий ответ на тот случай, если кому-то еще это понадобится.

Ключ использовал стандартную строку даты в запросе / ответе JSON вместе с надлежащими объектами Java 8 DateTime. То, что я отправлял, просто было недостаточно.

Таким образом, в соответствии с ISO-8601 форматами даты и времени:

Время выражается в UTC (Всемирное координированное время) со специальным обозначением UTC ("Z ").

Обратите внимание, что буква" T "появляется буквально в строке для обозначения начала элемента времени, как указано в ISO 8601.

Значение, мой APIдолжен общаться только в этих стандартизированных строках даты в формате UTC, а не в настраиваемых строках даты, например:

{
    "dateFrom": "2019-12-01T16:00:00Z",
    "dateTo": "2019-12-01T16:30:00Z"
}

Еще одна вещь, которую я сделал, как упоминалось в комментариях, была установка часового пояса БД в UTC и ничего не оставлял на волю случая,Так как я использую пружинную загрузку, было два способа сделать это:

  1. In application.properties установить свойство: spring.jpa.properties.hibernate.jdbc.time_zone=UTC

  2. Или вСтрока подключения jdbc: serverTimezone=UTC

Я также добавил параметр useLegacyDatetimeCode=false в строку выше, потому что я читал, что без него параметр serverTimezone не действует(кто-то может исправить меня, если я ошибаюсь по этому поводу).

Сейчас идут споры, есть ли у java.sql.Timestamp информация о часовом поясе или нет. Я прошел SO, чтобы узнать больше об этом, и оказалось, что в предыдущих версиях (до Java 8) действительно не было информации о часовом поясе (например, здесь ). Однако, как я ясно увидел в отладчике, у объекта Timestamp информация о зоне хранится отдельно, но она есть.

Согласно Mark Rotteveel из комментариев

a java.sql.Timestamp по спецификации представляет время в часовом поясе JVM по умолчанию

, что имеет смысл, потому что когда я изменил часовой пояс JVM, значения Timestamp также изменились.

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

System.setProperty("user.timezone", "UTC");

, и он работал, потому что теперь он конвертировал значения db в UTC, но что мне не понравилось в этом подходе, так это то, что он изменился все до времени UTC (дух), включая мои логи. Просто было неестественно делать это «насильно», как это.

Еще одно открытие, которое было для меня очень значимым, это то, что все говорили, что нужно полностью переключиться на java.time и егоклассы, которые дали 80% ответов на SO, довольно устарели и бесполезны, поскольку они предлагают использовать старые классы для таких вещей, как разбор, преобразование и т. д. (все еще не уверен, что смогу ли я полностью отказаться от java.sql.Timestamp, так какна данный момент документы говорят, что это тип для сопоставления с форматом datetime в БД, но сейчас я оставлю это).

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

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class DateTimeUtil {

    public static Timestamp getTimestamp(String utcDateTime) {
        LocalDateTime localDateTime = LocalDateTime.parse(utcDateTime, DateTimeFormatter.ISO_DATE_TIME);
        return Timestamp.from(localDateTime.atZone(ZoneId.of("UTC")).toInstant());
    }

    public static String getUTCString(Timestamp dbTimestamp) {
        return dbTimestamp.toInstant().atZone(ZoneId.of("UTC")).toString();
    }

}

Первая функция используется для анализа строковой даты из запроса JSON.

Я выбрал LocalDateTimeвместо ZonedDateTime из-за этой информации из java.time doc:

По возможности рекомендуется использовать более простой класс без часового пояса. Широкое использование часовых поясов обычно усложняет приложение.

( РЕДАКТИРОВАТЬ: Код был обновлен с использованием LocalDateTimeневерный, потому что он удаляет часовой пояс UTC, а затем ведет себя точно так же, как и в ситуации с JVM. Извините, я проверил это неправильно)

Вторая функция используется для отправки ответа в формате JSON. Пока значения хранятся в БД так, как они должны быть, когда я их получал обратно, они конвертировались в мой местный часовой пояс (так как JVM находится в другом часовом поясе). Вот почему нужно было сказать: «Эй, это значение - часовой пояс UTC, покажите это так, а не в моем местном часовом поясе».

Итак, прежде чем сохранить свою сущность в БД, я установил ее метку времени с помощью getTimestamp(), которая сохраняет его как UTC, и когда я получаю значение и готовлю свой DTO для ответа, я использую getUTCString(), который такжеделает его форматом UTC в формате ISO, при этом БД действует так, как будто она находится в UTC, поэтому она не может добавить что-то свое к миксу И связь между клиентом и API стандартизирована, и каждый точно знает, чего ожидать.

1 голос
/ 09 ноября 2019

Если вы хотите убедиться, что вы имеете дело с временем UTC, то вы должны сохранить их как таковые в базе данных. Вы можете указать зону или смещение в Java:

public ZonedDateTime utcDateTimeFrom(Timestamp timestamp) {
    return timestamp.toLocalDateTime().atZone(ZoneId.of("UTC"));
}

public Timestamp toUtcTimestamp(LocalDateTime localDateTime) {
    return Timestamp.from(localDateTime.atZone(ZoneId.of("UTC")).toInstant());
}

Попробуйте методы или напишите аналогичные (тоже есть OffsetDateTime), но я думаю, что дата и время в вашем ответе / запросе JSON:недостаточно, вам придется отправить часовой пояс или смещение со стороны клиента, иначе вы можете столкнуться с проблемами с часовыми поясами.

0 голосов
/ 08 ноября 2019

Часовой пояс приложения используется при преобразовании DateTime базы данных в java.sql.Timestamp. Вам нужно будет передать переменные окружения или установить их в приложении.

-Duser.timezone=America/Chicago

или

TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));

Редактировать:

Я думаю, вы упустили очень важный момент.

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

Когда вы создаете объект java.sql.Timestamp из некоторого строкового представления 2019-12-01 16: 00: 00 , там само ваше значение будет сдвинуто с использованием вашего сервера часовой пояс.

Приходите ко второму вопросу.

Например - 1573251160 - это время, когда я пишу этот ответ. Это значение будет означать один и тот же момент времени во всех часовых поясах.

Теперь его необходимо преобразовать в столбец DateTime для хранения в базе данных, которая является полем, зависящим от часового пояса. Не зная часовой пояс, вы просто не сможете выбрать время, так как время будет отличаться в разных часовых поясах. Без знания часового пояса его невозможно преобразовать. Например: То же значение будет преобразовано в:

  1. Пт, 08 ноября 2019 22:12:40 GMT
  2. Пт, 09 ноября 2019 3:42:40 GMT + 05: 30

Преобразование будет зависеть от того, в каком часовом поясе работает ваш сервер. Если вы всегда ожидаете, что ваши значения будут храниться в UTC, то разумно запустить ваш сервер в UTC.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...