如何在Spring Boot JPA中调用返回无主键复合表结果的SQL Server存储过程
针对你遇到的这种情况——存储过程返回的是来自多张表的复合字段,而且没有对应实体、也没有主键——其实Spring Boot JPA有两种靠谱的解决方式,我给你一步步拆解:
方案一:用EntityManager手动调用存储过程(灵活直接)
这种方法不需要定义额外的实体类,适合快速实现,步骤如下:
注入EntityManager
在你的Service类里注入EntityManager,用来执行存储过程:@Autowired private EntityManager entityManager;创建并配置存储过程查询
调用createStoredProcedureQuery方法指定存储过程名,然后注册输入参数,注意参数的顺序和类型要和SQL Server里的存储过程一致:StoredProcedureQuery query = entityManager.createStoredProcedureQuery("dbo.sp_query_client"); // 注册输入参数:第一个参数@sIdClient(必填INT) query.registerStoredProcedureParameter("sIdClient", Integer.class, ParameterMode.IN); // 注册第二个参数@iCodFamily(可选INT,默认NULL) query.registerStoredProcedureParameter("iCodFamily", Integer.class, ParameterMode.IN); // 设置参数值,比如调用时只传sIdClient的情况 query.setParameter("sIdClient", 2000); // 如果需要传第二个参数,就加上这行:query.setParameter("iCodFamily", 1);执行并处理结果
执行查询后,结果会以Object[]的形式返回,每个数组元素对应存储过程返回的一列,你可以手动把这些值映射到自定义的DTO里:List<Object[]> results = query.getResultList(); List<ClientFamilyDTO> dtoList = new ArrayList<>(); for (Object[] result : results) { ClientFamilyDTO dto = new ClientFamilyDTO(); dto.setCreationDate((Timestamp) result[0]); dto.setIdClient((Integer) result[1]); dto.setDvClient((String) result[2]); dto.setCodFamilyProd((Integer) result[3]); dto.setDescFamily((String) result[4]); dto.setQtyProduct((Integer) result[5]); dto.setBalance((Long) result[6]); dtoList.add(dto); }这里的
ClientFamilyDTO是你自己定义的普通Java类,包含对应字段的getter和setter就行,不需要任何JPA注解:public class ClientFamilyDTO { private Timestamp creationDate; private Integer idClient; private String dvClient; private Integer codFamilyProd; private String descFamily; private Integer qtyProduct; private Long balance; // 省略getter和setter方法 }
方案二:用@NamedStoredProcedureQuery配合结果映射(更规范)
如果想要更贴合JPA的规范写法,可以通过定义结果映射和存储过程查询来实现,步骤如下:
创建DTO并定义结果映射
先创建刚才的ClientFamilyDTO,然后需要一个空的实体类(或者用现有实体,如果有的话)来挂载@NamedStoredProcedureQuery和@SqlResultSetMapping——因为JPA的这些注解必须放在实体类上:@Entity @SqlResultSetMapping( name = "ClientFamilyResult", classes = @ConstructorResult( targetClass = ClientFamilyDTO.class, columns = { @ColumnResult(name = "creation_date", type = Timestamp.class), @ColumnResult(name = "id_client", type = Integer.class), @ColumnResult(name = "dv_client", type = String.class), @ColumnResult(name = "cod_family_prod", type = Integer.class), @ColumnResult(name = "desc_family", type = String.class), @ColumnResult(name = "qty_product", type = Integer.class), @ColumnResult(name = "balance", type = Long.class) } ) ) @NamedStoredProcedureQuery( name = "sp_query_client", procedureName = "dbo.sp_query_client", parameters = { @StoredProcedureParameter(name = "sIdClient", mode = ParameterMode.IN, type = Integer.class), @StoredProcedureParameter(name = "iCodFamily", mode = ParameterMode.IN, type = Integer.class) }, resultSetMappings = "ClientFamilyResult" ) public class DummyEntity { @Id private Long id; // 因为JPA实体必须有@Id,随便定义一个就行,不会用到数据库里的字段 }这里的
DummyEntity只是用来承载JPA注解的,不需要对应数据库里的表,@Id字段随便定义一个就行,因为我们不会操作这个实体的CRUD。调用存储过程
现在可以通过EntityManager来调用这个命名存储过程查询,直接获取DTO列表:StoredProcedureQuery query = entityManager.createNamedStoredProcedureQuery("sp_query_client"); query.setParameter("sIdClient", 1000); query.setParameter("iCodFamily", 1); List<ClientFamilyDTO> dtoList = query.getResultList();
注意事项
- 确保参数名称、类型和SQL Server存储过程里的完全一致,尤其是参数的
mode(IN/OUT/INOUT) - 如果存储过程有可选参数,设置时可以传
null,对应SQL里的默认值 - 处理结果时,要注意SQL返回的字段类型和Java类型的对应关系,比如SQL的
datetime对应Java的Timestamp,bigint对应Long
备注:内容来源于stack exchange,提问作者joseluisbz

