能否用Hibernate JPA返回非持久化实体的多实体联合查询结果集?
当然可以实现!完全不用给MyRsClass创建数据库表,Hibernate JPA提供了多种灵活的方式来直接将多实体查询的结果映射到自定义的非持久化类中,下面给你介绍几个最常用的方案:
1. JPQL构造函数映射(最直观的方式)
如果你的查询可以用JPQL而非原生SQL,直接用SELECT NEW语法就能把查询结果直接实例化到MyRsClass中,前提是你的MyRsClass要有匹配参数顺序和类型的构造函数。
示例代码:
首先定义MyRsClass,确保有对应构造函数:
public class MyRsClass { private String a; private String c; private String e; // 必须有和查询返回列顺序、类型完全匹配的构造函数 public MyRsClass(String a, String c, String e) { this.a = a; this.c = c; this.e = e; } // 可选:添加getter方法方便后续使用 public String getA() { return a; } public String getC() { return c; } public String getE() { return e; } }
然后在Repository接口里写JPQL查询:
@Repository public interface OneRepository extends JpaRepository<One, Long> { @Query("SELECT NEW com.yourpackage.MyRsClass(o.A, tw.C, th.E) " + "FROM One o, Two tw, Three th " + "WHERE o.oneFlag = tw.twoFlag AND o.oneFlag = th.threeFlag AND o.oneFlag = :aFlag") List<MyRsClass> findByFlag(@Param("aFlag") String aFlag); }
注意:这里要写全MyRsClass的完整包名,JPQL才能找到这个类。
2. 原生SQL配合@SqlResultSetMapping(适配原生查询场景)
如果你的业务必须用原生SQL(比如复杂查询JPQL不好写),可以用@SqlResultSetMapping配合@ConstructorResult来实现映射。
示例代码:
首先在任意一个持久化实体上(比如One实体)添加映射配置:
@Entity @SqlResultSetMapping( name = "MyRsClassMapping", classes = @ConstructorResult( targetClass = MyRsClass.class, columns = { @ColumnResult(name = "A", type = String.class), @ColumnResult(name = "C", type = String.class), @ColumnResult(name = "E", type = String.class) } ) ) public class One { // 实体原有字段和注解... private String A; private String B; private String oneFlag; }
然后在Repository的原生查询里引用这个映射:
@Repository public interface OneRepository extends JpaRepository<One, Long> { @Query(value = "SELECT o.A, tw.C, th.E " + "FROM One o, Two tw, Three th " + "WHERE o.oneFlag = tw.twoFlag AND o.oneFlag = th.threeFlag AND o.oneFlag = :aFlag", nativeQuery = true, resultSetMapping = "MyRsClassMapping") List<MyRsClass> findByFlag(@Param("aFlag") String aFlag); }
这里要注意原生SQL返回的列名要和@ColumnResult里的name完全一致,如果原生查询里有别名,也要对应上。
3. Spring Data JPA投影(更灵活的选择)
如果你用的是Spring Data JPA,还可以用**投影(Projection)**来实现,分两种形式:
3.1 类投影(和JPQL构造函数方式类似)
其实就是前面第一种方案的简化版,Spring Data会自动处理构造函数的调用,写法和第一种几乎一致,核心还是依赖类的匹配构造函数。
3.2 接口投影(更轻量)
定义一个接口,里面只包含要返回字段的getter方法,Hibernate会动态生成代理类来返回结果,不需要写实体类的构造函数:
public interface MyRsProjection { String getA(); String getC(); String getE(); }
然后在Repository里直接返回这个接口的列表:
@Repository public interface OneRepository extends JpaRepository<One, Long> { // JPQL写法 @Query("SELECT o.A AS a, tw.C AS c, th.E AS e " + "FROM One o, Two tw, Three th " + "WHERE o.oneFlag = tw.twoFlag AND o.oneFlag = th.threeFlag AND o.oneFlag = :aFlag") List<MyRsProjection> findByFlag(@Param("aFlag") String aFlag); // 原生SQL写法(需要给列加别名,和接口的getter对应) @Query(value = "SELECT o.A AS a, tw.C AS c, th.E AS e " + "FROM One o, Two tw, Three th " + "WHERE o.oneFlag = tw.twoFlag AND o.oneFlag = th.threeFlag AND o.oneFlag = :aFlag", nativeQuery = true) List<MyRsProjection> findByFlagNative(@Param("aFlag") String aFlag); }
注意:接口的getter方法名要和查询结果的别名对应(比如getA()对应别名a,或者如果不写别名,JPQL里的字段名要和getter去掉get后的小写匹配)。
注意事项
- 不管用哪种方式,查询返回的列的顺序、类型必须和目标类/接口的字段/getter完全匹配,否则会抛出映射异常。
- 原生SQL查询时,列名(或别名)一定要和映射配置里的名字对应,因为数据库返回的列名可能是大写或小写,要注意大小写匹配。
- 如果
MyRsClass里有其他字段,要么给这些字段设置默认值,要么确保查询结果能覆盖到所有必填字段。
内容的提问来源于stack exchange,提问作者jscriptor

