如何用Criteria API为日期加天数?PostgreSQL+Hibernate报错解决
需要筛选出无法在客户指定截止日期前完成订单的DeliveryMethod,其中DeliveryMethod的deliveryDeadlineAmount字段(int类型)表示准备订单所需天数。
当前编写的JPA Criteria代码如下:
... LocalDate deadlineByCustomer = LocalDate.now().plusDays(2); ... Join<Site, DeliveryMethod> deliveryMethod = site.join(Site.Fields.deliveryMethods, JoinType.INNER); if (deadlineByCustomer != null) { Predicate deliveryMethodCanSatisfyDeadline = criteriaBuilder.lessThanOrEqualTo( criteriaBuilder.function("DATEADD", LocalDate.class, criteriaBuilder.literal(TemporalUnit.DAY), deliveryMethod.get(DeliveryMethod.Fields.deliveryDeadlineAmount), criteriaBuilder.literal(LocalDate.now()) ), criteriaBuilder.literal(deadlineByCustomer)); predicates.add(deliveryMethodCanSatisfyDeadline); } ...
运行时抛出错误:
Cannot invoke "org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource.getSqmPathType()" because the return value of "org.hibernate.query.sqm.tree.expression.SqmLiteral.getNodeType()" is null
此前尝试用criteriaBuilder.literal("day")替代criteriaBuilder.literal(TemporalUnit.DAY),但出现错误:
Parameter 1 of function timestampadd() has type TEMPORAL_UNIT, but argument is of type java.lang.Object
疑问:使用环境为PostgreSQL + Hibernate,不理解为何Hibernate会调用timestampadd()而非指定的原生SQL函数DATEADD。
1. 为什么会调用timestampadd()而非DATEADD
Hibernate的JPA Criteria API中,function()方法并非直接映射原生SQL函数名称,而是优先匹配Hibernate预定义的JPQL标准函数别名。DATEADD是SQL Server专属函数,PostgreSQL没有该函数;Hibernate会将日期加减的标准操作映射为JPQL的timestampadd函数,最终再转换为PostgreSQL的原生语法(比如current_date + ?1 * interval '1 day')。
2. 正确实现方式
方式一:使用Hibernate封装的日期运算API(推荐,跨数据库)
直接调用criteriaBuilder.plus()完成日期加天数的计算,代码更简洁且无需关注数据库原生函数:
if (deadlineByCustomer != null) { // 计算当前日期加上准备天数后的截止日期 Expression<LocalDate> calculatedDeadline = criteriaBuilder.plus( criteriaBuilder.literal(LocalDate.now()), deliveryMethod.get(DeliveryMethod.Fields.deliveryDeadlineAmount) ); // 筛选"计算出的截止日期晚于客户指定日期"的记录(即无法按时完成) Predicate cannotMeetDeadline = criteriaBuilder.greaterThan( calculatedDeadline, criteriaBuilder.literal(deadlineByCustomer) ); predicates.add(cannotMeetDeadline); }
方式二:显式使用JPQL标准timestampadd函数
如果要手动调用函数,需遵循JPQL标准格式,传入正确的TemporalUnit参数:
if (deadlineByCustomer != null) { Predicate cannotMeetDeadline = criteriaBuilder.greaterThan( criteriaBuilder.function( "timestampadd", LocalDate.class, criteriaBuilder.literal(TemporalUnit.DAY), deliveryMethod.get(DeliveryMethod.Fields.deliveryDeadlineAmount), criteriaBuilder.literal(LocalDate.now()) ), criteriaBuilder.literal(deadlineByCustomer) ); predicates.add(cannotMeetDeadline); }
3. 关键注意事项
- 原代码逻辑是筛选能按时完成的记录,若需求是筛选无法按时完成的,需将
lessThanOrEqualTo改为greaterThan。 - 不要用字符串
"day"作为时间单位参数,必须传入TemporalUnit.DAY,Hibernate会自动处理参数类型映射,避免类型错误。
内容的提问来源于stack exchange,提问作者swarts97

