【问题标题】:What is the JPQL equivalent of the PostgreSQL date_trunc('day', (entity.date AT TIME ZONE 'UTC')) function when using JPA and Hibernate使用 JPA 和 Hibernate 时,PostgreSQL date_trunc('day', (entity.date AT TIME ZONE 'UTC')) 函数的 JPQL 等效项是什么?
【发布时间】:2019-04-29 04:22:26
【问题描述】:

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

我需要在 Spring Boot 应用程序中四舍五入到全天(和几周等),Hibernate 在 Postgresql 数据库之上运行。这个查询在本机 postgresql 中是可能的,但我还没有弄清楚如何在 Hibernate 中执行它。时区(如“UTC”、“Europe/Amsterdam”)和日期部分(如“日”、“年”)可能会有所不同,因此我不能将其设置为默认值。

我在 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"

编辑:cmets on answer。

弗拉德的回答很棒,我对其进行了更多调整,以便将日期部分也作为变量。此外,我必须覆盖标准的 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 也是一个好主意。为了解决我所有的时区问题,我也必须这样做。

【问题讨论】:

    标签: java spring hibernate jpa jpql


    【解决方案1】:

    正如Hibernate User Guide 中所述,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();
    

    使用 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 配置属性将SqlFunctionsMetadataBuilderContributor 提供给Hibernate:

    <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 存储库中创建了以下测试用例,它们运行良好:

    【讨论】:

    • Extract 与 date_trunc 不太一样。 date_trunc 仍然给我整个日期。我在分组查询中使用它来获取一定数量的日期的计数。时区是可变的。我现在将完整的查询编辑到我的帖子中。看起来我可以按照您的方式进行,或者直接使用完整的本机查询。
    • 查看我的更新答案。它甚至包含一个测试用例的链接,就像一个魅力。
    • 听起来不错,虽然我确实需要传递时区 id 字符串和截断周期(小时、分钟),但它们是可变的。
    • 不。它在 persistence.xml 文件中
    • 如果在Spring Boot的application.properties文件中添加spring.jpa.properties.hibernate.metadata_builder_contributor配置属性,应该就可以正常工作了。
    【解决方案2】:

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

    function('date_trunc', 'month', date)
    

    举个例子-

    select distinct w from YourModel w where studentId=:studentId and function('date_trunc', 'month', date) = :month
    

    【讨论】:

      【解决方案3】:

      除了@karthiks3000 的回答,如果你使用 Hibernate 的 PostgreSQL 方言,你可以简单地在 JPQL 查询中调用它的函数:

      date_trunc('month', date)
      

      来源:How do you call a PostgreSQL function with Hibernate?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-14
        • 2015-09-28
        • 2021-11-30
        • 2021-11-12
        • 2015-03-12
        • 2012-04-04
        • 1970-01-01
        相关资源
        最近更新 更多