Spring Data JPA关联无关系三表:原生查询返回多实体方案
你遇到的核心问题是:你的SLSIWorkflowRepository泛型是SLSIWorkflow,Spring Data JPA会自动把原生查询的结果映射到这个实体类上,其他两个表的字段因为不属于SLSIWorkflow,所以被直接忽略了。下面给你两种最实用的解决方案:
方案一:创建DTO封装多表数据(推荐用于报表场景)
这种方式不需要修改现有实体的关联关系,专门为报表查询封装数据,代码侵入性最低。
步骤1:创建报表专用DTO
新建一个DTO类,用来封装三个表需要的所有数据(也可以直接把三个实体作为属性):
public class ReportDataDTO { private SLSNotification slsNotification; private SLSIWorkflow slsiWorkflow; private Importer importer; // 构造函数要和JPQL查询里的参数顺序一致 public ReportDataDTO(SLSNotification slsNotification, SLSIWorkflow slsiWorkflow, Importer importer) { this.slsNotification = slsNotification; this.slsiWorkflow = slsiWorkflow; this.importer = importer; } // 生成三个属性的getter方法 public SLSNotification getSlsNotification() { return slsNotification; } public SLSIWorkflow getSlsiWorkflow() { return slsiWorkflow; } public Importer getImporter() { return importer; } }
步骤2:修改Repository的查询语句(改用JPQL构造函数查询)
把原生查询改成JPQL,直接通过构造函数把三个实体封装到DTO里:
public interface SLSIWorkflowRepository extends CrudRepository<SLSIWorkflow,Long> { // 替换原来的getForPrint方法 @Query("SELECT new com.yourpackage.ReportDataDTO(a, b, c) " + "FROM SLSNotification a " + "JOIN SLSIWorkflow b ON a.snumber = b.SNumber " + "JOIN Importer c ON c.importerVatNumber = a.importerVAT " + "WHERE a.importerVAT = :importerVatNumber AND a.snumber = :snumber") ReportDataDTO getForPrint(@Param("snumber") Long snumber, @Param("importerVatNumber") String importerVatNumber); // 其他原有方法... }
注意:把
com.yourpackage替换成你实际的DTO包路径。
步骤3:修改Service和Controller
更新Service方法的返回类型:
public ReportDataDTO getApplicationForPrint(Long SNumber, String importerVatNumber) { return workflowRepository.getForPrint(SNumber, importerVatNumber); }
Controller层不需要额外修改,Jasper报表里可以通过slsNotification.serialCord、slsiWorkflow.qc、importer.importerName这样的表达式直接访问三个表的数据。
方案二:建立实体间的关联关系(适合通用业务场景)
如果后续业务中也需要频繁关联这三个表,可以给实体添加关联注解,通过JPA的fetch join一次性获取所有关联数据。
步骤1:修改实体类添加关联
给SLSIWorkflow添加与SLSNotification的一对一关联:
@Entity @Table(name = "sls_wrkflw") public class SLSIWorkflow { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "snumber") // 这里要明确指定数据库列名,避免大小写映射问题 private Long SNumber; // 添加一对一关联 @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "snumber", referencedColumnName = "snumber", insertable = false, updatable = false) private SLSNotification slsNotification; // 其他原有字段和getter/setter... }
给SLSNotification添加与Importer的多对一关联:
@Entity @Table(name = "slsnotification") public class SLSNotification { // 原有字段... @Column(length = 20, nullable = false) private String importerVAT; // 添加多对一关联 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "importervat", referencedColumnName = "importer_vat_number", insertable = false, updatable = false) private Importer importer; // 其他原有字段和getter/setter... }
步骤2:修改Repository的查询语句(使用fetch join)
通过fetch join一次性加载关联的实体,避免懒加载导致的N+1查询问题:
public interface SLSIWorkflowRepository extends CrudRepository<SLSIWorkflow,Long> { @Query("SELECT b FROM SLSIWorkflow b " + "JOIN FETCH b.slsNotification a " + "JOIN FETCH a.importer c " + "WHERE a.importerVAT = :importerVatNumber AND a.snumber = :snumber") SLSIWorkflow getForPrint(@Param("snumber") Long snumber, @Param("importerVatNumber") String importerVatNumber); // 其他原有方法... }
步骤3:直接使用关联数据
查询返回的SLSIWorkflow对象中,slsNotification和importer已经被初始化,Jasper报表里可以通过slsNotification.importer.importerName、slsNotification.serialCord这样的路径访问所有数据。
注意事项
- 实体类的字段名要和数据库列名严格对应,比如
SLSIWorkflow的SNumber字段要加上@Column(name = "snumber"),否则Spring Data JPA会默认映射为s_number,导致关联失败。 - 如果使用方案二,关联注解中的
insertable = false, updatable = false是因为外键已经由业务逻辑维护,不需要JPA自动更新。
内容的提问来源于stack exchange,提问作者Nipun Vidarshana

