
JPA 原生 JPQL 不支持在 SELECT NEW 中嵌套 new 表达式(如 new Bar(x) 作为另一个构造器参数),但可通过重构目标类的构造函数,将嵌套对象的字段直接传入,由 Java 构造逻辑完成对象组装。
jpa 原生 jpql 不支持在 `select new` 中嵌套 `new` 表达式(如 `new bar(x)` 作为另一个构造器参数),但可通过重构目标类的构造函数,将嵌套对象的字段直接传入,由 java 构造逻辑完成对象组装。
在 Spring Data JPA 中,使用 SELECT NEW 投影构造自定义 DTO 是常见做法,但 JPQL 规范明确限制:NEW 关键字后只能跟一个类名和括号内的字段/表达式列表,不支持嵌套 new 调用。因此,像 new Foo(a, new Bar(b)) 这样的写法会编译失败或抛出 QuerySyntaxException。
✅ 正确解法是:将嵌套对象所需的原始字段(而非对象实例)作为参数传入顶层构造器,并在 Java 层完成组合逻辑。
以你的 Foo 和 Bar 类为例:
public class Foo {
private final String a;
private final Bar bar;
// ✅ 新增:接受原始字段 b 的构造器(关键!)
public Foo(String a, int b) {
this(a, new Bar(b)); // 在 Java 中构造 Bar,JPQL 只需传 b
}
// ✅ 保留原有构造器,供其他场景使用
public Foo(String a, Bar bar) {
this.a = a;
this.bar = bar;
}
// 建议添加 getter(JPA 投影虽不依赖 getter,但利于测试与调试)
public String getA() { return a; }
public Bar getBar() { return bar; }
}
public class Bar {
private final int b;
public Bar(int b) {
this.b = b;
}
public int getB() { return b; }
}
对应 Repository 中的 JPQL 查询即可简洁书写:
@Repository
public interface FooRepository extends JpaRepository<entity long> {
@Query("SELECT NEW bla.blub.Foo(e.a, e.b) FROM Entity e")
List<foo> lol();
// 或使用 JPQL 别名简化(效果相同)
@Query("SELECT NEW bla.blub.Foo(e.a, e.b) FROM Entity e WHERE e.active = true")
List<foo> findActiveFoos();
}</foo></foo></entity>
⚠️ 注意事项:
- 类路径
bla.blub.Foo必须为全限定名(含包名),且该类必须有对应参数类型的public 构造器; -
Entity是你实际映射的 JPA 实体类名(如FooEntity),请替换为真实类名; - 所有参数类型必须严格匹配构造器签名(
String对e.a,int对e.b),避免类型不匹配异常; - 若字段为
null,int类型将触发自动拆箱异常(NullPointerException),建议改用Integer并增加空值判断,或在查询中用COALESCE(e.b, 0)处理; - 此方案完全基于 JPQL,无需启用 Hibernate 特有的
@SqlResultSetMapping或原生 SQL,保持可移植性。
? 总结:JPA 的 SELECT NEW 是“字段到构造器参数”的扁平映射机制,不支持对象图构建。将嵌套逻辑下沉至 Java 构造器,既符合规范,又保持代码清晰与类型安全——这是 Spring Data JPA 中处理分层 DTO 投影的标准实践。











