簡體   English   中英

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

[英]How to query JPA LocalDateTime field with a LocalDate value?

我正在尋找一種方法來獲取在某個 LocalDateTime 日期創建的對象列表,該對象保存在 Postgresql 數據庫中 TIMESTAMPTZ 類型的字段中。

為此,我嘗試使用 JpaRepository:

List<Object> findByCreationDate(LocalDate date);

但我得到了錯誤:

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

我也嘗試自己編寫查詢,結果相同。

到目前為止我想到的解決方案:

  1. 獲取 Java 中的所有對象和過濾器(但我不想在那里 go,所以沒有)

  2. 轉換 LocalDateTime 中的 LocalDate 參數(但我假設在這種情況下我只會看到在同一確切時間創建的對象,所以不會。只有當我確定時間字段是午夜時,我才會考慮這個選項)。

  3. 創建一個方法findByCreationDateBetween(LocalDateTime startOfTheDay, LocalDateTime endOfTheDay) (有趣的選項,但我想在不修改 LocalDate 並在一天的開始和結束時將其轉換為 LocalDateTime 的情況下進行查詢。)

我還嘗試找到一些能夠在查詢中“投射”LocalDate 中的 LocalDateTime 或比較 LocalDate 和 LocalDateTime 的年、月和日的函數,但沒有成功。 creationDate 的類型應保持 LocalDateTime 和 TIMESTAMPTZ 類型的數據庫字段。

是否還有其他 Jpa @Query 替代品,總體而言,這種情況下的最佳方法是什么?

您當前的解決方案很好。 但是在@Query中,您可以使用DATE()轉換列

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

我不使用 JPA、Hibernate 或 Spring,所以我必須忽略您問題的這一方面。 我正在使用直接 JDBC 代替。

一天的時間范圍需要一個時區

您似乎忽略了時區的關鍵問題。 對於任何給定的時刻,日期在全球范圍內因時區而異。 在某個時刻,在日本東京可能是“明天”,而在美國俄亥俄州托萊多仍然是“昨天”。

因此,當您指定從一天開始到第二天開始的日期范圍時,您必須牢記時區。

從分區時間轉換為 UTC 以獲取搜索條件

你說:

Postgresql 數據庫中 TIMESTAMPTZ 類型的字段

Postgres中的類型名稱TIMESTAMPTZ是標准類型名稱TIMESTAMP WITH TIME ZONE的非標准縮寫。

Postgres 將所有提交的數據存儲在 UTC 中該類型的列中,與 UTC 的偏移量為零小時-分鍾-秒。 提交的輸入隨附的任何偏移量或時區都用於首先將值調整為 UTC 以進行存儲。 存儲后,任何提交的區域/偏移信息都將被丟棄。 Postgres 以 UTC 存儲所有TIMESTAMP WITH TIME ZONE值。

因此,如果要查詢一天內的值,必須首先在所需時區中定義一天,然后將當天的開始和結束時間調整為 UTC 值。

String input = "2020-12-12" ;
LocalDate ld = LocalDate.parse( input ) ;

確定在您選擇的時區中看到的第一個時刻。 始終讓java.time確定一天的第一時刻,永遠不要假設一天從 00:00:00 開始。 某些區域的某些日期的某些日子從其他時間開始,例如 01:00:00。

通常最好在日期時間處理中使用半開方法定義時間跨度。 開始是包容的,而結束是排斥的。 這允許跨度整齊地彼此鄰接。

因此,您的 SQL 查詢使用BETWEEN 您的 SQL 將如下所示。 請注意,說“等於或晚於”的更短的方式是“不早於”(SQL 中的!< )。

SELECT * 
FROM event_
WHERE when_ !< ? 
AND when_ < ? 
;

您的 Java 看起來像這樣。

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtStart = ld.atStartOfDay( z ) ;
ZonedDateTime zdtEnd = ld.plusDays( 1 ).atStartOfDay( z ) ;

2020-12-12T00:00+13:00[太平洋/奧克蘭]/2020-12-13T00:00+13:00[太平洋/奧克蘭]

通過提取Instant對象來適應 UTC。 根據定義, Instant對象始終采用 UTC。

Instant start = zdtStart.toInstant() ;
Instant end = zdtEnd.toInstant() ;

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

請參閱在 IdeOne.com 上實時運行的代碼

你的 JDBC 看起來像這樣:

myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , end ) ;

在 JDBC 代碼中,我們傳遞了Instant對象。 這可能會或可能不會起作用,具體取決於您的 JDBC 驅動程序。 奇怪的是,JDBC 4.2 規范需要支持OffsetDateTime但不是更常用的InstantZonedDateTime類型。 沒關系,我們可以輕松轉換。

myPreparedStatement.setObject( 1 , start.atOffset( ZoneOffset.UTC ) ) ;
myPreparedStatement.setObject( 2 , end.atOffset( ZoneOffset.UTC ) ) ;

我們可以通過調用ZonedDateTime::toOffsetDateTimeZonedDateTime轉到OffsetDateTime 但是這些對象的偏移量不會是 UTC(零)。 您的 JDBC 驅動程序和數據庫應該能夠處理這個問題。 但出於謹慎考慮,並且為了調試和記錄,我會使用 UTC,如上所示。 但僅供參考,代碼:

myPreparedStatement.setObject( 1 , zdtStart.toOffsetDateTime() ) ;
myPreparedStatement.setObject( 2 , zdtEnd.toOffsetDateTime() ) ;

永遠不要使用LocalDateTime

在處理時間軸上的特定點時,切勿使用LocalDateTime 缺少區域/偏移量的上下文, LocalDateTime不能代表一個時刻。


Java(傳統和現代)和標准 SQL 中的日期時間類型表

使用發布后被快速刪除的答案(我必須使用列而不是屬性名稱,將查詢標記為 nativeQuery 以及 select 與 '.*' 以避免org.postgresql.util.PSQLException: The column name id was not found in this ResultSet error),我找到了這個解決方案:

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

或者,按照這個答案,我成功地測試了第二個解決方案:

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


List<Entity> findByCreationDateBetween(LocalDateTime from, LocalDateTime to);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM