Spring非Boot6项目JPQL用java.util.Date触发SQLGrammarException问题
解决Spring 6 JPA查询中的SQLGrammarException:无法确定参数数据类型
问题原因分析
触发SQLGrammarException: could not determine data type of parameter $2的核心原因有两个:
- 服务层参数传递错误
服务层getTrainingsByTraineeUsername方法调用DAO时,错误地将toDate参数替换成了fromDate:
trainingDao.getTrainingsByTraineeUsername( username, fromDate, fromDate, trainerName, trainingType);
当请求仅传入dateFrom、dateTo为null时,DAO的toDate参数变为null,Hibernate无法从null值推断对应的SQL数据类型,导致PostgreSQL报错。
- null参数的类型推断缺陷
使用java.util.Date作为参数时,若参数为null,Hibernate无法明确其对应的SQL类型(如DATE/TIMESTAMP),在对类型严格的PostgreSQL中会触发类型识别失败。
分步解决方案
1. 修复服务层参数传递错误
将服务层调用DAO的代码中第三个参数改为正确的toDate:
var trainings = trainingDao.getTrainingsByTraineeUsername( username, fromDate, toDate, trainerName, trainingType);
2. 解决null参数类型推断问题
可以选择以下两种方式之一:
方式一:明确指定参数类型
在DAO的查询中,给日期参数指定TemporalType,确保Hibernate能识别参数类型,即使参数为null:
entityManager.createQuery(jpql, Training.class) .setParameter("username", username) .setParameter("fromDate", fromDate, TemporalType.DATE) // 明确DATE类型 .setParameter("toDate", toDate, TemporalType.DATE) .setParameter("trainerName", trainerName) .setParameter("trainingType", trainingType) .getResultList();
方式二:使用Criteria API动态构建查询
通过动态拼接条件,避免将null参数传入查询,从根源上解决类型推断问题:
@Override public List<Training> getTrainingsByTraineeUsername( String username, Date fromDate, Date toDate, String trainerName, TrainingType trainingType) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Training> cq = cb.createQuery(Training.class); Root<Training> trainingRoot = cq.from(Training.class); Join<Training, Trainee> traineeJoin = trainingRoot.join("trainee"); List<Predicate> conditions = new ArrayList<>(); // 基础条件:匹配学员用户名 conditions.add(cb.equal(traineeJoin.get("user").get("username"), username)); // 动态添加日期范围条件 if (fromDate != null) { conditions.add(cb.greaterThanOrEqualTo(trainingRoot.get("date"), fromDate)); } if (toDate != null) { conditions.add(cb.lessThanOrEqualTo(trainingRoot.get("date"), toDate)); } // 动态添加教练姓名条件 if (trainerName != null && !trainerName.isBlank()) { conditions.add(cb.equal(trainingRoot.get("trainer").get("user").get("firstName"), trainerName)); } // 动态添加培训类型条件 if (trainingType != null) { conditions.add(cb.equal(trainingRoot.get("type"), trainingType)); } cq.where(conditions.toArray(new Predicate[0])); return entityManager.createQuery(cq).getResultList(); }
3. 可选优化:替换Java 8日期类型
推荐用java.time.LocalDate替代java.util.Date,Spring 6对Java 8+日期API支持更完善,无需额外处理类型推断:
- 控制器参数改为
LocalDate dateFrom,@DateTimeFormat(pattern = "yyyy-MM-dd")可直接适配 - 实体类
Training的date字段改为LocalDate,JPA自动映射到SQL的DATE类型 - DAO和服务层的日期参数同步改为
LocalDate
验证修复
修改后重新发送请求http://localhost:8082/api/v1/trainee/yevhenii.kashtalian/trainings?dateFrom=2020-02-20,此时toDate为null,但参数传递正确且类型明确,查询可正常执行并返回符合条件的培训记录。
内容的提问来源于stack exchange,提问作者yekashtalian
相关产品推荐
相关产品推荐

