spring-boot-starter-data-jpa 2.6.4 依赖于 spring-data-jpa 2.6.2,后者依赖于 hsqldb 2.5.2 和 hibernate 5.6.7.Final
当我们想要查询 CrudRepository 中 java.sql.Timestamp 类型的多个值时,即
@Query("SELECT dm " +
" FROM DataModel dm " +
"WHERE (dm.ts1 IS NULL OR :ts1 IS NULL OR dm.ts1 < :ts1) " +
" AND (dm.ts2 IS NULL OR :ts2 IS NULL OR dm.ts2 < :ts2)")
List<DataModel> findByTimestamps(@Param("ts1") Timestamp ts1, @Param("ts2") Timestamp ts2);
它失败了
org.hsqldb.HsqlException: incompatible data type in conversion
spring-data-jpa 2.6.3 也会发生这种情况
降级到 hsqldb 2.5.1 可以工作
用于重现的存储库: https://github.com/daveyx/spring-data-hsqldb-timestamp-issue
问题出现在
org.hsqldb.jdbc.JDBCPreparedStatement#setTimestamp(int, java.sql.Timestamp, java.util.Calendar)
,因为参数类型错误。
看起来 @Param 注释参数的“IS NULL”部分导致 JDBCPreparedStatement#setTimestamp 中的“CharacterType”而不是“DateTimeType”。
正如您提到的,问题是由条件
OR :ts1 IS NULL
和OR :ts2 IS NULL
引起的。 Hibernate 将这些以 ? IS NULL
的形式应用于 SQL 查询,并且 HSQLDB 将参数解析为字符串。然后 Hibernate 假设参数类型解析为 java.sql.Timestamp 并调用 setTimestamp(int, Timestamp) 方法,这会导致问题。
可以添加CAST来确定参数的类型。
@Query("SELECT dm " +
" FROM DataModel dm " +
"WHERE (dm.ts1 IS NULL OR CAST(:ts1 AS TIMESTAMP) IS NULL OR dm.ts1 < :ts1) " +
" AND (dm.ts2 IS NULL OR CAST(:ts2 AS TIMESTAMP) IS NULL OR dm.ts2 < :ts2)")
HSQLDB 版本 2.5.1 对
setTimestamp(int, Timestamp)
比较宽松,当目标不是时间戳时,将时间戳转换为字符串。 2.5.2 和 2.6.0 版本中的回归使其更加严格。 HSQLDB 的未来版本将恢复到宽松的方法。
@fredt 我对日期也有同样的问题,将有助于修复以下查询的空检查:
@Query("SELECT p FROM PromotionCodeDraft p WHERE " +
"(:personId IS NULL OR p.requester.id = :personId) AND " +
"(:promotionCode IS NULL OR p.promotionCode LIKE %:promotionCode%) AND " +
"(:workflowStatus IS NULL OR p.workflowStatus = :workflowStatus) AND " +
"(:customerLabel IS NULL OR p.customerLabel LIKE %:customerLabel%) AND " +
"((:active IS NULL) OR " +
"(:active = false AND (p.active = false OR p.active IS NULL)) OR " +
"(:active = true AND p.active = true)) AND " +
"((:startDate IS NULL AND :endDate IS NULL) OR " +
"(:startDate IS NOT NULL AND :endDate IS NULL AND p.startDate = :startDate) OR " +
"(:endDate IS NOT NULL AND :startDate IS NULL AND p.endDate = :endDate) OR " +
"(:startDate IS NOT NULL AND :endDate IS NOT NULL AND p.startDate >= :startDate AND p.endDate <= :endDate))")