What is the JPQL/JPA/Hibernate equivalent of the database function date_trunc('day', (entity.date AT TIME ZONE 'UTC'))
?
I need to be rounding down to full days (and weeks, etc.) in a Spring Boot application with Hibernate running on top of a Postgresql database. This query is possible in native postgresql but I haven't figured out how to do it in Hibernate yet. The timezone (like 'UTC', 'Europe/Amsterdam') and date part (like 'day', 'year') may vary so I can't just set it to a default.
I found an ancient post at the Hibernate forums without any replies: https://forum.hibernate.org/viewtopic.php?f=1&t=990451
I also found a related StackOverflow question where the poster directly wants to select on a date as a certain timezone. It has no replies though: HQL equivalent of Postgres' "datetime at time zone"
My whole query (without the where) looks like this so a different way to achieve this would be ok too.
"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's answer is great, I adapted it some more to also have the date part as a variable. Additionally I had to override the standard Hibernate behavior which generates Date
columns as timestamp without timezone
and explicitly make them timestamp with timezone
by putting this on my column definition:
@Column(columnDefinition = "TIMESTAMP WITH TIME ZONE") private Date date;
For further reading, see https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29
It's also still a good idea to use the Hibernate property hibernate.jdbc.time_zone
, according to Vlad's other blog post https://vladmihalcea.com/how-to-store-date-time-and-timestamps-in-utc-time-zone-with-jdbc-and-hibernate/ and start your JVM in UTC. I had to do this too in order to fix all my timezone issues.
In PostgreSQL, DATE_TRUNC Function is used to truncate a timestamp type or interval type with specific and high level of precision. Syntax: date_trunc('datepart', field) The datepart argument in the above syntax is used to truncate one of the field,below listed field type: millennium. century.
Returns timestamp truncated to the unit specified in field .
Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that's actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.
JPA supports LocalDate , LocalTime , LocalDateTime etc. but not Instant.
As explained in the Hibernate User Guide, yu can use either the EXTRACT
function:
List<Integer> days = entityManager
.createQuery(
"select extract( day from c.timestamp ) " +
"from Call c ", Integer.class )
.getResultList();
or the day
function:
List<Integer> days = entityManager
.createQuery(
"select day( c.timestamp ) " +
"from Call c ", Integer.class )
.getResultList();
MetadataBuilderContributor
If you need to use the AT TIMEZONE
as well, you need to register the PostgreSQL function like this:
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
using JPA persistence.xmlNow, you need to provide the SqlFunctionsMetadataBuilderContributor
to Hibernate via the hibernate.metadata_builder_contributor
configuration property:
<property>
name="hibernate.metadata_builder_contributor"
value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"
</property>
MetadataBuilderContributor
using Spring BootIf you're using Spring Boot, you need to add the following configuration to the application.properties
file:
spring.jpa.properties.hibernate.metadata_builder_contributor=com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor
Then, assuming you have the following Post
entity in the database:
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);
You can call the date_trunc
function in JPQL like this:
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")
);
If you want to customize the time zone, just pass the time zone as a parameter like this:
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))"
)
);
}
}
Now, you can call the date_trunc
function as follows:
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();
I created the following test cases in my high-performance-java-persistence GitHub repository, and they run just fine:
DateTruncUtcFunctionTest
DateTruncTimeZoneFunctionTest
You can simply call a postgres function using the following syntax -
function('date_trunc', 'month', date)
As an example -
select distinct w from YourModel w where studentId=:studentId and function('date_trunc', 'month', date) = :month
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With