17370845950

Spring Data JPA 中使用 @Query 返回多列数据时的正确方式

当在 spring data jpa 中通过 `@query` 原生 sql 或 jpql 查询多列并期望返回 `tuple` 时,若配置不当(如误用原生 sql + `tuple`),会触发 `indexoutofboundsexception`;根本原因在于 spring boot 1.5.22+ 对原生查询与 `tuple` 的兼容性限制增强,需改用构造器表达式或自定义 dto。

在 Spring Boot 升级至 1.5.22.RELEASE 及更高版本后,@Query 注解对原生 SQL(nativeQuery = true)与 Tuple 类型的组合支持被严格限制:原生 SQL 不支持直接映射为 Tuple(即使使用别名),这导致 SELECT q.id as someId, q.name as someName 这类多列查询在执行时无法正确构建 Tuple 实例,最终抛出 IndexOutOfBoundsException: Index: 0, Size: 0 —— 表明结果集为空或元数据解析失败。

✅ 正确做法是:改用 JPQ

L(非原生) + 构造器表达式(constructor expression),显式调用目标类的构造方法。例如,定义一个轻量级 DTO:

// src/main/java/com/example/demo/TupleDto.java
public class TupleDto {
    private final Long someId;
    private final String someName;

    public TupleDto(Long someId, String someName) {
        this.someId = someId;
        this.someName = someName;
    }

    // getters (required for projection usage)
    public Long getSomeId() { return someId; }
    public String getSomeName() { return someName; }
}

然后在 Repository 中使用 JPQL 构造器语法(注意:不能加 value =,且必须是合法 JPQL,非 SQL):

@Repository
public interface QuoteRepository extends JpaRepository {

    @Query("SELECT new com.example.demo.TupleDto(q.id, q.name) " +
           "FROM Quote q WHERE q.id IN :quoteIds")
    List selectSomeThings(@Param("quoteIds") List quoteIds);
}

⚠️ 注意事项:

  • ❌ @Query(value = "...", nativeQuery = true) + List 不适用于多列原生查询(单列可能偶然成功,属未定义行为);

  • ✅ Tuple 仅可靠用于 JPQL 查询 + select new org.hibernate.query.Tuple(...)(Hibernate 特有)或更推荐的 接口投影(Interface-based Projection)

  • ✅ 若坚持用 Tuple,应改用 JPQL 并启用 Hibernate 的 Tuple 构造(需确保使用 Hibernate 5.2+):

    @Query("SELECT NEW org.hibernate.query.Tuple(q.id AS someId, q.name AS someName) FROM Quote q WHERE q.id IN :quoteIds")
    List selectAsTuple(@Param("quoteIds") List quoteIds); // 需确认 Hibernate 版本兼容性
  • ? 替代方案:使用 接口投影(推荐),更类型安全、无需手动建类:

    interface QuoteProjection {
        Long getSomeId(); // 对应 SELECT 中的别名
        String getSomeName();
    }
    
    @Query("SELECT q.id AS someId, q.name AS someName FROM Quote q WHERE q.id IN :quoteIds")
    List selectProjections(@Param("quoteIds") List quoteIds);

? 总结:该问题本质是 Spring Boot 1.5.22+ 加强了对原生查询与 Tuple 组合的校验。解决核心在于——放弃原生 SQL + Tuple 多列组合,转向 JPQL 构造器、DTO 投影或接口投影。三者中,接口投影最简洁,DTO 最灵活可控,而 Tuple 仅建议在动态列场景下配合 JPQL 谨慎使用。