Spring Data JPA中含自定义对象的类投影查询问题
解决方案:Spring Data JPA 投影查询 Foo 附带 Bar 的 Age 属性
问题分析
你尝试的两种投影方案失败的原因如下:
- 封闭投影方案:直接返回实体别名的方式未被Spring Data正确解析,JPA对实体类型的投影属性映射需要严格的规则匹配,未指定完整字段映射时会导致投影失效。
- 类投影方案:
new Foo(f)的写法错误,JPA不能通过实体实例作为参数调用构造函数创建新实体对象,直接传递查询得到的f即可。
方案1:修正类投影(最简洁直接)
步骤1:定义投影类
构造方法直接接收Foo实体和Integer类型的age,无需手动创建Foo实例:
public class FooWithAge { private final Foo foo; private final Integer age; // 构造方法参数顺序、类型必须与查询语句完全匹配 public FooWithAge(Foo foo, Integer age) { this.foo = foo; this.age = age; } // 生成getter方法 public Foo getFoo() { return foo; } public Integer getAge() { return age; } }
步骤2:修正@Query语句
使用实体直接作为构造参数,并且指定投影类的全限定名:
@Query("select new com.yourpackage.FooWithAge(f, b.age) from Foo f inner join Bar b on f.barId = b.id") List<FooWithAge> query2();
注意:替换
com.yourpackage为实际包路径,JPA需要明确投影类的完整位置。
方案2:正确使用封闭投影接口
步骤1:定义投影接口
使用开放投影(通过SpEL表达式直接获取属性),无需在查询中指定别名:
public interface FooWithAge { // 直接获取Foo实体 @Value("#{target.f}") Foo getFoo(); // 通过SpEL从关联的Bar中获取age @Value("#{target.b.age}") Integer getAge(); }
步骤2:编写查询语句
查询时直接关联Foo和Bar,无需别名:
@Query("select f, b from Foo f inner join Bar b on f.barId = b.id") List<FooWithAge> query1();
方案3:利用实体关联关系(更优雅)
如果允许修改实体类,建议在Foo中添加与Bar的多对一关联,后续查询会更简洁:
步骤1:修改Foo实体
@Entity public class Foo { @Id private Long id; private String name; // 添加多对一关联,映射barId字段 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "barId") private Bar bar; // getters and setters }
步骤2:定义投影接口
通过默认方法直接从关联的Bar中获取age:
public interface FooWithAge { Foo getFoo(); default Integer getAge() { return getFoo().getBar().getAge(); } }
步骤3:编写查询(避免N+1问题)
使用join fetch一次性加载关联的Bar:
@Query("select f from Foo f join fetch f.bar") List<FooWithAge> findAllWithBarAge();
这种方式不需要额外构造投影类,也不需要手动关联字段,完全利用JPA的实体关联机制。
内容的提问来源于stack exchange,提问作者Daniel Pop
相关产品推荐
相关产品推荐

