使用 JPA 和 Hibernate 时,PostgreSQL date_trunc('day', (entity.date AT TIME ZONE 'UTC')) 函数的 JPQL 等价物是什么
What is the JPQL equivalent of the PostgreSQL date_trunc('day', (entity.date AT TIME ZONE 'UTC')) function when using JPA and Hibernate
数据库函数 date_trunc('day', (entity.date AT TIME ZONE 'UTC'))
的 JPQL/JPA/Hibernate 等价物是什么?
我需要在 Postgresql 数据库之上使用 Hibernate 运行 在 Spring 引导应用程序中四舍五入到整日(和周等)。此查询在本机 postgresql 中是可能的,但我还没有弄清楚如何在 Hibernate 中执行此操作。时区(如 'UTC'、'Europe/Amsterdam')和日期部分(如 'day'、'year')可能会有所不同,因此我不能将其设置为默认值。
我在 Hibernate 论坛上发现了一个古老的 post 没有任何回复:https://forum.hibernate.org/viewtopic.php?f=1&t=990451
我还发现了一个相关的 Whosebug 问题,其中 poster 直接想要 select 在某个时区的日期。虽然它没有回复: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 的其他博客 post 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 配置 MetadataBuilderContributor
persistence.xml
现在,您需要通过 hibernate.metadata_builder_contributor
配置向 Hibernate 提供 SqlFunctionsMetadataBuilderContributor
属性:
<property>
name="hibernate.metadata_builder_contributor"
value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"
</property>
使用 Spring 引导配置 MetadataBuilderContributor
如果你使用Spring启动,你需要在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();
测试用例
我在我的高性能-java-持久性GitHub 存储库中创建了以下测试用例,它们运行 很好:
您可以使用以下语法简单地调用 postgres 函数 -
function('date_trunc', 'month', date)
举个例子-
select distinct w from YourModel w where studentId=:studentId and function('date_trunc', 'month', date) = :month
除了@karthiks3000 的回答,如果你使用 Hibernate 的 PostgreSQL 方言,你可以简单地在 JPQL 查询中调用它的函数:
date_trunc('month', date)
数据库函数 date_trunc('day', (entity.date AT TIME ZONE 'UTC'))
的 JPQL/JPA/Hibernate 等价物是什么?
我需要在 Postgresql 数据库之上使用 Hibernate 运行 在 Spring 引导应用程序中四舍五入到整日(和周等)。此查询在本机 postgresql 中是可能的,但我还没有弄清楚如何在 Hibernate 中执行此操作。时区(如 'UTC'、'Europe/Amsterdam')和日期部分(如 'day'、'year')可能会有所不同,因此我不能将其设置为默认值。
我在 Hibernate 论坛上发现了一个古老的 post 没有任何回复:https://forum.hibernate.org/viewtopic.php?f=1&t=990451
我还发现了一个相关的 Whosebug 问题,其中 poster 直接想要 select 在某个时区的日期。虽然它没有回复: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 的其他博客 post 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 配置 MetadataBuilderContributor
persistence.xml
现在,您需要通过 hibernate.metadata_builder_contributor
配置向 Hibernate 提供 SqlFunctionsMetadataBuilderContributor
属性:
<property>
name="hibernate.metadata_builder_contributor"
value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"
</property>
使用 Spring 引导配置 MetadataBuilderContributor
如果你使用Spring启动,你需要在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();
测试用例
我在我的高性能-java-持久性GitHub 存储库中创建了以下测试用例,它们运行 很好:
您可以使用以下语法简单地调用 postgres 函数 -
function('date_trunc', 'month', date)
举个例子-
select distinct w from YourModel w where studentId=:studentId and function('date_trunc', 'month', date) = :month
除了@karthiks3000 的回答,如果你使用 Hibernate 的 PostgreSQL 方言,你可以简单地在 JPQL 查询中调用它的函数:
date_trunc('month', date)