java - 使用 JPA 和 Hibernate 时,PostgreSQL date_trunc ('day' , (entity.date AT TIME ZONE 'UTC' )) 函数的 JPQL 等价物是什么

标签 java spring hibernate jpa jpql

数据库函数 date_trunc('day', (entity.date AT TIME ZONE 'UTC')) 的 JPQL/JPA/Hibernate 等价物是什么?

我需要在 Spring Boot 应用程序中四舍五入到全天(和周等),Hibernate 运行在 Postgresql 数据库之上。这个查询在 native postgresql 中是可能的,但我还没有想出如何在 Hibernate 中进行。时区(如“UTC”、“欧洲/阿姆斯特丹”)和日期部分(如“日”、“年”)可能会有所不同,因此我不能将其设置为默认值。

我在 Hibernate 论坛上发现了一个没有任何回复的旧帖子:https://forum.hibernate.org/viewtopic.php?f=1&t=990451

我还发现了一个相关的 StackOverflow 问题,发帖人直接想选择某个日期作为某个时区。虽然它没有回复:HQL equivalent of Postgres' "datetime at time zone"

我的整个查询(没有 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"

Edit: comments on answer.

Vlad 的回答很好,我对其进行了一些调整,将日期部分也作为变量。此外,我必须重写标准的 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

根据 Vlad 的其他博文 https://vladmihalcea.com/how-to-store-date-time-and-timestamps-in-utc-time-zone-with-jdbc-and-hibernate/,使用 Hibernate 属性 hibernate.jdbc.time_zone 也是一个好主意。并以 UTC 启动 JVM。为了解决我所有的时区问题,我也必须这样做。

最佳答案

Hibernate User Guide 中所述, 你可以使用 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();

使用 MetadataBuilderContributor 注册 DATE_TRUNC 函数

如果你还需要使用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'))"
                )
        );
    }
}

使用 JPA persistence.xml 配置 MetadataBuilderContributor

现在,您需要通过 hibernate.metadata_builder_contributor 配置属性向 Hibernate 提供 SqlFunctionsMetadataBuilderContributor:

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

使用 Spring Boot 配置 MetadataBuilderContributor

如果您使用的是 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);

在 JPQL 中使用 DATE_TRUNC

您可以像这样在 JPQL 中调用 date_trunc 函数:

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();

测试用例

我在我的 high-performance-java-persistence GitHub 存储库中创建了以下测试用例,它们运行得很好:

关于java - 使用 JPA 和 Hibernate 时,PostgreSQL date_trunc ('day' , (entity.date AT TIME ZONE 'UTC' )) 函数的 JPQL 等价物是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53499478/

相关文章:

java - 如何使用泛型限制对象创建?

Java - 内联表达式的资源释放

spring - JHipster - Spring 。 Oauth2 资源服务器配置。创建资源服务器

java - Spring-AngularJS-文件上传-org.apache.commons.fileupload.FileUploadException : the request was rejected because no multipart boundary was found

javascript - 当数据库中的表发生更改时通知用户

java - Android、apk 安装 INSTALL_FAILED_POLICY_REJECTED_PERMISSION

java - Spring Framework 5 中的问题接线 JPA 存储库

hibernate - JPA 等效于 Hibernate.initialize 的命令

java - JPA Hibernate实体异常: org. hibernate.MappingException

java - 接口(interface)函数定义