it-swarm.com.ru

Как указать часовой пояс UTC для Spring Boot JPA Timestamp

Среда

  • Spring Boot Starter Data JPA 1.4.2
  • Eclipselink 2.5.0 
  • Postgresql 9.4.1211.jre7

Проблема

Я создаю микросервис Spring Boot, который совместно использует базу данных Postgresql с другим сервисом. База данных инициализируется извне (вне нашего контроля), а тип столбца datetime, используемый другой службой, - отметка времени без часового пояса . Поэтому, поскольку я хочу, чтобы все даты в БД имели одинаковый тип, наличие этого типа является обязательным требованием для моих дат сущностей JPA.

То, как я отображаю их на объектах сущности JPA, выглядит следующим образом:

@Column(name = "some_date", nullable = false)
private Timestamp someDate;

Проблема в том, что когда я создаю метку времени следующим образом: 

new Java.sql.Timestamp(System.currentTimeMillis())

и я смотрю на базу данных, отметка времени содержит дату и время моего местного часового пояса, но я хочу сохранить ее в формате UTC. Это связано с тем, что для моего часового пояса по умолчанию установлено значение «Европа/Брюссель», а JPA/JDBC преобразует мой объект Java.sql.Timestamp в мой часовой пояс, прежде чем поместить его в базу данных.

Найдены не идеальные решения

  • TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); имеет эффект, которого я хочу достичь, но он не подходит, потому что это не относится к моей службе. То есть это повлияет на всю JVM или текущий поток плюс дочерние.

  • Запуск приложения с помощью -Duser.timezone=GMT, по-видимому, также выполняет работу для отдельного экземпляра работающей JVM. Поэтому лучшее решение, чем предыдущее.

Но есть ли способ указать часовой пояс в конфигурации загрузки JPA/datasource/spring?

7
oizulain

Наиболее подходящий обходной путь, который я мог бы найти для этой проблемы, - это использование AttributeConverter для преобразования объектов Java 8 ZonedDateTime в объекты Java.sql.Timestamp, чтобы они могли быть сопоставлены с типом PostgreSQL timestamp without time zone

Причина, по которой вам нужен AttributeConverter, заключается в том, что типы даты и времени Java 8/Joda еще не совместимы с JPA .

AttributeConverter выглядит так:

@Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
        return (zonedDateTime == null ? null : Timestamp.valueOf(zonedDateTime.toLocalDateTime()));
    }

    @Override
    public ZonedDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
        return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime().atZone(ZoneId.of("UTC")));
    }

}

Это позволяет мне читать временные метки базы данных, которые не содержат информацию о часовом поясе, как объекты ZonedDateTime, которые имеют часовой пояс UTC. Таким образом, я сохраняю точное время, которое можно увидеть на db , независимо от часового пояса, в котором работает мое приложение

Поскольку toLocalDateTime() также применяет системное преобразование часового пояса по умолчанию, это AttributeConverter в основном отменяет преобразование, применяемое драйвером JDBC.

Вам действительно нужно использовать timestamp without timezone?

Реальность такова, что если вы храните информацию о дате и времени в часовом поясе (даже если это UTC), тип timestamp without timezone PostgreSQL является неправильным выбором. Правильный тип данных для использования будет timestamp with timezone, который включает информацию о часовом поясе. Подробнее по этой теме здесь .

Однако если по какой-либо причине вы должны использовать timestamp without timezone, я думаю, что описанный выше подход ZonedDateTime является надежным и последовательным решением.

Вы также сериализуете ZonedDateTime в JSON?

Тогда вас, вероятно, заинтересует тот факт, что вам нужна хотя бы версия 2.6.0 зависимости jackson-datatype-jsr310 для работы сериализации. Подробнее об этом в этом ответе .

6
oizulain

Ты не можешь Eclipselink использует одну версию arg setTimestamp, делегируя ответственность за обработку часового пояса драйверу, а драйвер jdbc postgresql не позволяет переопределить часовой пояс по умолчанию. Драйвер postgres даже передает часовой пояс клиента в сеанс, поэтому настройки по умолчанию на стороне сервера также не будут вам полезны.

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

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

1
teppic