如何使用 LocalDate 值查询 JPA LocalDateTime 字段?

use*_*862 6 java jpa spring-data-jpa java-time spring-repositories

我寻找一种方法来获取在 Postgresql 数据库中 TIMESTAMPTZ 类型的字段中保存的某个 LocalDateTime 日期上创建的对象列表。

为此,我尝试使用 JpaRepository:

List<Object> findByCreationDate(LocalDate date);
Run Code Online (Sandbox Code Playgroud)

但我收到错误:

java.lang.IllegalArgumentException: Parameter value [2020-12-12] did not match expected type [java.time.LocalDateTime (n/a)]
Run Code Online (Sandbox Code Playgroud)

我也尝试用相同的结果自己编写查询。

到目前为止我想到的解决方案:

  1. 在 Java 中获取所有对象和过滤器(但我不想去那里,所以不)

  2. 在 LocalDateTime 中转换 LocalDate 参数(但我假设在这种情况下我只会看到在同一时间创建的对象,所以不会。只有当我确定时间字段是午夜时,我才会考虑这个选项)。

  3. 创建一个方法findByCreationDateBetween(LocalDateTime startOfTheDay, LocalDateTime endOfTheDay)(有趣的选项,但我想在不修改 LocalDate 并在一天开始和结束时将其转换为 LocalDateTime 的情况下进行查询。)

我还试图找到一些能够在查询中“转换”LocalDate 中的 LocalDateTime 或比较 LocalDate 和 LocalDateTime 的年、月和日的函数,但没有成功。creationDate 的类型应保持 LocalDateTime 和 TIMESTAMPTZ 类型的数据库字段。

是否还有其他 Jpa @Query 替代方案,总体而言,这种情况下的最佳方法是什么?

use*_*862 10

使用发布后很快被删除的答案(我必须使用列而不是属性名称,将查询标记为nativeQuery,并使用“.*”进行选择以避免org.postgresql.util.PSQLException: The column name id was not found in this ResultSet错误),我找到了这个解决方案:

@Query(value = "SELECT e.* FROM Entity e WHERE DATE(creation_date) =:date", nativeQuery = true)
List<Entity> findByCreationDate(LocalDate date);
Run Code Online (Sandbox Code Playgroud)

或者,按照这个答案,我成功测试了第二个解决方案:

default List<Entity> findByCreationDate(LocalDate localDate) {
    return findByPublicationDateBetween(localDate.atStartOfDay(), localDate.plusDays(1).atStartOfDay());
}


List<Entity> findByCreationDateBetween(LocalDateTime from, LocalDateTime to);
Run Code Online (Sandbox Code Playgroud)


Ekl*_*vya 5

您当前的解决方案很好。但@Query你可以使用DATE()

@Query(value = "SELECT * FROM Entity u WHERE DATE(creation_date) = ?1", nativeQuery = true)
List<Entity> findByCreationDate(LocalDate date);
Run Code Online (Sandbox Code Playgroud)

  • 请注意,在列上使用函数可能会使数据库无法使用该列上可能存在的任何索引。因此,之间的查询可能会更有效。 (3认同)

Bas*_*que 5

我不使用 JPA、Hibernate 或 Spring,所以我必须忽略你问题的那个方面。我使用的是直接的 JDBC。

一天的时间范围需要一个时区

您似乎忽略了时区的关键问题。对于任何给定时刻,日期在全球各地因时区而异。在某个时刻,在日本东京可能是“明天”,而在美国俄亥俄州托莱多仍然是“昨天”。

因此,当您指定从一天开始到第二天开始的日期范围时,您必须考虑一个时区。

将分区时间转换为 UTC 以获取搜索条件

你说:

TIMESTAMPTZ 类型字段中的 Postgresql 数据库

TIMESTAMPTZ Postgres 中的类型名称是标准类型名称的非标准缩写TIMESTAMP WITH TIME ZONE

Postgres 将所有提交的数据以 UTC 格式存储在该类型的列中,与 UTC 的偏移量为零时-分-秒。随提交输入的任何偏移量或时区首先用于将值调整为 UTC 以进行存储。存储后,任何提交的区域/偏移量信息都将被丢弃。PostgresTIMESTAMP WITH TIME ZONE以 UTC格式存储所有值。

因此,如果您想查询一天中的值,您必须首先在所需时区中定义一天,然后将该天的开始和结束调整为 UTC 值。

String input = "2020-12-12" ;
LocalDate ld = LocalDate.parse( input ) ;
Run Code Online (Sandbox Code Playgroud)

确定在您选择的时区中看到的第一个时刻。始终让java.time确定一天中的第一个时刻,永远不要假设一天从 00:00:00 开始。某些区域中某些日期的某些天在其他时间开始,例如 01:00:00。

通常最好在日期时间处理中使用半开方法定义时间跨度。开始是包容的,而结束是排他的。这允许跨距整齐地相互邻接。

所以,你的SQL查询并不能使用BETWEEN。您的 SQL 将类似于以下内容。请注意,更短的说法“等于或晚于”是“不在之前”(!<在 SQL 中)。

SELECT * 
FROM event_
WHERE when_ !< ? 
AND when_ < ? 
;
Run Code Online (Sandbox Code Playgroud)

您的 Java 看起来像这样。

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtStart = ld.atStartOfDay( z ) ;
ZonedDateTime zdtEnd = ld.plusDays( 1 ).atStartOfDay( z ) ;
Run Code Online (Sandbox Code Playgroud)

2020-12-12T00:00+13:00[太平洋/奥克兰]/2020-12-13T00:00+13:00[太平洋/奥克兰]

通过提取Instant对象调整到 UTC 。Instant根据定义,对象始终采用 UTC 格式。

Instant start = zdtStart.toInstant() ;
Instant end = zdtEnd.toInstant() ;
Run Code Online (Sandbox Code Playgroud)

2020-12-11T11:00:00Z/2020-12-12T11:00:00Z

查看此代码在 IdeOne.com 上实时运行

您的 JDBC 如下所示:

myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , end ) ;
Run Code Online (Sandbox Code Playgroud)

在那个 JDBC 代码中,我们传递Instant对象。这可能有效,也可能无效,具体取决于您的 JDBC 驱动程序。奇怪的是,JDBC 4.2规范要求的支持OffsetDateTime而不是更常用InstantZonedDateTime类型。没关系,我们可以轻松转换。

myPreparedStatement.setObject( 1 , start.atOffset( ZoneOffset.UTC ) ) ;
myPreparedStatement.setObject( 2 , end.atOffset( ZoneOffset.UTC ) ) ;
Run Code Online (Sandbox Code Playgroud)

我们可以通过调用从ZonedDateTime到。但是这些对象的偏移量不会是UTC(零)。您的 JDBC 驱动程序和数据库应该能够处理。但出于谨慎考虑,为了调试和日志记录,我将使用 UTC,如上所示。但仅供参考,代码:OffsetDateTimeZonedDateTime::toOffsetDateTime

myPreparedStatement.setObject( 1 , zdtStart.toOffsetDateTime() ) ;
myPreparedStatement.setObject( 2 , zdtEnd.toOffsetDateTime() ) ;
Run Code Online (Sandbox Code Playgroud)

切勿使用LocalDateTime的时刻

LocalDateTime处理时刻时切勿使用时间轴上的特定点。缺少区域/偏移量的上下文,aLocalDateTime不能代表片刻。


Java(旧版和现代版)和标准 SQL 中的日期时间类型表