Что является эквивалентом JPQL функции PostgreSQL date_trunc ('day', (entity.date AT TIME ZONE 'UTC')) при использовании JPA и Hibernate - PullRequest
0 голосов
/ 27 ноября 2018

Что такое эквивалент JPQL / JPA / Hibernate для функции базы данных date_trunc('day', (entity.date AT TIME ZONE 'UTC'))?

Мне нужно округлить до полных дней (и недель и т. Д.) В приложении Spring Boot с запущенным Hibernateповерх базы данных Postgresql.Этот запрос возможен в нативном postgresql, но я еще не выяснил, как это сделать в Hibernate.Часовой пояс (например, «UTC», «Европа / Амстердам») и часть даты (например, «день», «год») могут отличаться, поэтому я не могу просто установить для них значение по умолчанию.

Я нашелДревний пост на форумах Hibernate без каких-либо ответов: https://forum.hibernate.org/viewtopic.php?f=1&t=990451

Я также нашел связанный вопрос StackOverflow, в котором постер непосредственно хочет выбрать дату в качестве определенного часового пояса.Тем не менее, он не имеет ответов: Эквивалент HQL для "datetime at time zone" Postgres

Весь мой запрос (без использования where) выглядит следующим образом, поэтому для достижения этого можно использовать другой способ.тоже.

"SELECT NEW " + AggregateQueryEntity.class.getName() +
                "(date_trunc('" + aggregationPeriod.toString() +
                "', a.date), a.alertConfiguration.id.id, a.alertConfiguration.name, a.alertLevel, count(*)) from Alert a" +
                whereClause.toString() +
                " GROUP BY 1, a.alertConfiguration.id.id, a.alertConfiguration.name, a.alertLevel" +    //column 1 is the truncated date
                " ORDER BY 1, alertLevel DESC"

Редактировать: комментарии к ответу.

Ответ Влада великолепен, я еще немного его адаптировал, чтобы часть даты также была переменной.Кроме того, мне пришлось переопределить стандартное поведение Hibernate, которое генерирует столбцы Date как timestamp without timezone и явно сделать их timestamp with timezone, добавив это в определение моего столбца:

@Column(columnDefinition = "TIMESTAMP WITH TIME ZONE") private Date date;

Для дальнейшего чтения см. https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29

По-прежнему неплохо использовать свойство Hibernate hibernate.jdbc.time_zone, согласно другому сообщению в блоге Влада https://vladmihalcea.com/how-to-store-date-time-and-timestamps-in-utc-time-zone-with-jdbc-and-hibernate/, и запустить свою JVM в UTC.Мне тоже пришлось сделать это, чтобы исправить все проблемы с часовым поясом.

1 Ответ

0 голосов
/ 27 ноября 2018

Это очень интересный вопрос, поэтому я решил превратить в статью .

Как описано в Руководстве пользователя yu может использовать функцию EXTRACT:

List<Integer> days = entityManager
.createQuery(
    "select extract( day from c.timestamp ) " +
    "from Call c ", Integer.class )
.getResultList();

или функцию day:

List<Integer> days = entityManager
.createQuery(
    "select day( c.timestamp ) " +
    "from Call c ", Integer.class )
.getResultList();

Регистрация функции DATE_TRUNC с использованием MetadataBuilderContributor

Если вам также нужно использовать AT TIMEZONE, вам необходимо зарегистрировать функцию PostgreSQL следующим образом:

public class SqlFunctionsMetadataBuilderContributor
        implements MetadataBuilderContributor {

    @Override
    public void contribute(MetadataBuilder metadataBuilder) {
        metadataBuilder.applySqlFunction(
                "date_trunc",
                new SQLFunctionTemplate(
                        StandardBasicTypes.TIMESTAMP,
                        "date_trunc('day', (?1 AT TIME ZONE 'UTC'))"
                )
        );
    }
}

Настройка MetadataBuilderContributor с использованием JPA persistence.xml

Теперь,вам нужно предоставить SqlFunctionsMetadataBuilderContributor для Hibernate через свойство конфигурации hibernate.metadata_builder_contributor:

<property>
    name="hibernate.metadata_builder_contributor" 
    value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"
</property>

Настройка MetadataBuilderContributor с использованием Spring Boot

Если вы используете Spring Boot, вынеобходимо добавить следующую конфигурацию в файл application.properties:

spring.jpa.properties.hibernate.metadata_builder_contributor=com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor

Данные тестирования

Затем, при условии, что в базе данных имеется следующая сущность Post:

Post post = new Post();
post.setId(1L);
post.setTitle(
    "High-Performance Java Persistence"
);
post.setCreatedOn(
    Timestamp.valueOf(
        LocalDateTime.of(2018, 11, 23, 11, 22, 33)
    )
);

entityManager.persist(post);

Использование DATE_TRUNC в JPQL

Вы можете вызвать функцию date_trunc в JPQL следующим образом:

Tuple tuple = entityManager
.createQuery(
    "select p.title as title, date_trunc(p.createdOn) as creation_date " +
    "from Post p " +
    "where p.id = :postId", Tuple.class)
.setParameter("postId", 1L)
.getSingleResult();

assertEquals(
    "High-Performance Java Persistence", 
    tuple.get("title")
);

assertEquals(
    Timestamp.valueOf(
        LocalDateTime.of(2018, 11, 23, 0, 0, 0)
    ), 
    tuple.get("creation_date")
);

Для получения более подробной информации, ознакомьтесь с этой статьей .

Настройка часового пояса, переданного в DATE_TRUNC

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

public static class SqlFunctionsMetadataBuilderContributor
        implements MetadataBuilderContributor {

    @Override
    public void contribute(MetadataBuilder metadataBuilder) {
        metadataBuilder.applySqlFunction(
                "date_trunc",
                new SQLFunctionTemplate(
                        StandardBasicTypes.TIMESTAMP,
                        "date_trunc('day', (?1 AT TIME ZONE ?2))"
                )
        );
    }
}

Теперь вы можете вызвать функцию date_trunc следующим образом:

Tuple tuple = entityManager
.createQuery(
    "select p.title as title, date_trunc(p.createdOn, :timezone) as creation_date " +
    "from Post p " +
    "where p.id = :postId", Tuple.class)
.setParameter("postId", 1L)
.setParameter("timezone", "UTC")
.getSingleResult();

Контрольные примеры

Я создал следующий тестслучаи в моем высокопроизводительном хранилище GitHub-java-persistence, и они работают нормально:

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