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

Seb*_*oek 4 java spring hibernate jpa jpql

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等同于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"
Run Code Online (Sandbox Code Playgroud)

编辑:对答案的评论。

弗拉德的答案很好,我对其进行了一些修改,以便将日期部分也作为变量。另外,我必须重写标准的Hibernate行为,该行为Date将按生成列,timestamp without timezonetimestamp with timezone通过将其放在我的列定义中来显式地创建它们:

@Column(columnDefinition = "TIMESTAMP WITH TIME ZONE") private Date date;
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29

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

Vla*_*cea 6

这是一个非常有趣的问题,因此我决定上一篇文章

用户指南中所述,yu可以使用以下任一EXTRACT功能:

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

day功能:

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

使用以下命令注册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'))"
                )
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

配置MetadataBuilderContributor使用JPA persistence.xml

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

<property>
    name="hibernate.metadata_builder_contributor" 
    value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"
</property>
Run Code Online (Sandbox Code Playgroud)

配置MetadataBuilderContributor使用Spring Boot

如果您使用的是Spring Boot,则需要在application.properties文件中添加以下配置:

spring.jpa.properties.hibernate.metadata_builder_contributor=com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor
Run Code Online (Sandbox Code Playgroud)

测试数据

然后,假设您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);
Run Code Online (Sandbox Code Playgroud)

在JPQL中使用DATE_TRUNC

您可以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")
);
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请查看本文

自定义传递给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))"
                )
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以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();
Run Code Online (Sandbox Code Playgroud)

测试用例

我在高性能Java持久性GitHub存储库中创建了以下测试用例,它们运行良好:

  • 但是,提取与 date_trunc 并不完全相同。date_trunc 仍然给我整个日期。我在 group by 查询中使用它来获取一定数量的日期的计数。时区是可变的。我现在将完整的查询编辑到我的帖子中。看起来我可以按照您的方式进行,或者只是进行完整的本机查询。 (2认同)
  • 如果在Spring Boot的`application.properties`文件中添加`spring.jpa.properties.hibernate.metadata_builder_contributor`配置属性,应该可以正常工作。 (2认同)

kar*_*000 5

您可以使用以下语法简单地调用 postgres 函数 -

function('date_trunc', 'month', date)
Run Code Online (Sandbox Code Playgroud)

举个例子 -

select distinct w from YourModel w where studentId=:studentId and function('date_trunc', 'month', date) = :month
Run Code Online (Sandbox Code Playgroud)