Spring Boot 中使用原生 SQL 查询的正确写法

在 spring data jpa 中,若需执行包含子查询、表别名或数据库特有语法(如 postgresql 的函数调用)的复杂 sql,必须显式声明 `nativequery = true`,否则 jpa 会将其误判为 jpql 并报语法错

误。

Spring Data JPA 默认将 @Query 注解中的语句解析为 JPQL(Java Persistence Query Language),而 JPQL 是面向实体对象的抽象查询语言,不支持原生 SQL 的语法特性,例如:

  • 子查询作为 FROM 子句(即派生表/内联视图)
  • 显式表别名(如 AS travelA_query)
  • 数据库特定函数(如 com.example.imse22.model.TrvlA_Cust_Dto(...))
  • 原生 GROUP BY 字段别名或非 SELECT 列引用

你遇到的错误 unexpected token '(' after FROM 正是因为 JPA 尝试用 JPQL 解析器去处理一条标准的 PostgreSQL 原生 SQL —— 而 JPQL 不允许 FROM (SELECT ...) 这种结构。

✅ 正确做法:明确指定 nativeQuery = true,并使用 value 属性传递纯 SQL 字符串:

@Query(
    value = "SELECT com.example.imse22.model.TrvlA_Cust_Dto(books_query.name, COUNT(travelA_query.customer_id)) " +
            "FROM (SELECT DISTINCT customer_servant.employee_id, books.customer_id " +
            "      FROM customer_servant " +
            "      INNER JOIN books ON customer_servant.employee_id = books.customer_servant_id) AS travelA_query " +
            "INNER JOIN " +
            "(SELECT travel_agency.id, travel_agency.name, employee.employee_id " +
            " FROM travel_agency " +
            " INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) AS books_query " +
            "ON travelA_query.employee_id = books_query.employee_id " +
            "GROUP BY books_query.name",  // ⚠️ 注意:原文中拼写错误为 'GROUOP',已修正
    nativeQuery = true
)
List findTravelAgencyCustomerCounts();

? 关键要点:

  • ✅ 必须添加 nativeQuery = true,否则 Spring 仍按 JPQL 解析;
  • ✅ 使用 value 属性(而非无名字符串)以提高可读性与 IDE 支持;
  • ✅ 确保 SQL 语法完全符合目标数据库(PostgreSQL),JPA 不做任何翻译;
  • ✅ 返回类型应为 POJO(如 TrvlA_Cust_Dto),且该类需提供与查询结果列顺序/别名匹配的构造函数(若使用构造函数映射)——但注意:原生查询无法直接调用 Java 构造函数(如 TrvlA_Cust_Dto(...)),除非你改用 @SqlResultSetMapping 或改用 JdbcTemplate。更推荐方式是返回 Object[] 或定义 @SqlResultSetMapping,或简化为返回基础字段再手动封装;
  • ❌ 避免在原生查询中直接写 com.example.imse22.model.TrvlA_Cust_Dto(...) —— 这是 Java 类构造调用,PostgreSQL 无法识别,会导致数据库层面报错。应改为返回原始字段,再在服务层组装 DTO:
-- 推荐写法(返回字段,由 Java 处理构造)
SELECT books_query.name AS name, COUNT(travelA_query.customer_id) AS customer_count
FROM ...
GROUP BY books_query.name

然后在 Repository 方法中返回 List 或定义接口投影(interface TrvlA_Cust_Projection { String getName(); Long getCustomerCount(); }),实现类型安全与解耦。

? 总结:当 SQL 复杂度超出 JPQL 能力范围时,拥抱 nativeQuery = true,但务必同步承担 SQL 数据库绑定、SQL 注入防护(避免字符串拼接参数)、以及结果映射责任——这是灵活性与控制力的合理代价。