You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring非Boot6项目JPQL用java.util.Date触发SQLGrammarException问题

解决Spring 6 JPA查询中的SQLGrammarException:无法确定参数数据类型

问题原因分析

触发SQLGrammarException: could not determine data type of parameter $2的核心原因有两个:

  1. 服务层参数传递错误
    服务层getTrainingsByTraineeUsername方法调用DAO时,错误地将toDate参数替换成了fromDate:
trainingDao.getTrainingsByTraineeUsername(
    username, fromDate, fromDate, trainerName, trainingType);

当请求仅传入dateFrom、dateTo为null时,DAO的toDate参数变为null,Hibernate无法从null值推断对应的SQL数据类型,导致PostgreSQL报错。

  1. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:43:19