如何用Spring Data JPA调用存储过程并映射到无对应实体的自定义对象
Spring Data JPA调用存储过程映射自定义DTO(无需实体类)
针对多表关联存储过程返回的结果集,以下是几种可行的实现方案,无需为结果集创建对应实体类:
方案1:@SqlResultSetMapping + @ConstructorResult(JPA标准方式)
这种方式利用JPA的结果集映射注解,将存储过程返回的列直接映射到自定义DTO的构造函数,无需实体类对应结果表。
步骤1:定义自定义DTO
创建包含目标字段的DTO,并提供与存储过程返回列顺序匹配的构造函数:
import java.math.BigDecimal; public class EmployeeSalaryDTO { private String empName; private String deptName; private BigDecimal salary; // 构造函数参数顺序必须和存储过程返回列的顺序一致 public EmployeeSalaryDTO(String empName, String deptName, BigDecimal salary) { this.empName = empName; this.deptName = deptName; this.salary = salary; } // 按需添加getter方法 public String getEmpName() { return empName; } public String getDeptName() { return deptName; } public BigDecimal getSalary() { return salary; } }
步骤2:定义结果集映射
由于JPA要求@SqlResultSetMapping必须绑定到实体类,可创建一个空的占位实体类来承载映射:
import javax.persistence.*; import java.math.BigDecimal; @Entity @SqlResultSetMapping( name = "EmployeeSalaryDTOMapping", classes = @ConstructorResult( targetClass = EmployeeSalaryDTO.class, columns = { @ColumnResult(name = "emp_name", type = String.class), @ColumnResult(name = "dept_name", type = String.class), @ColumnResult(name = "salary", type = BigDecimal.class) } ) ) public class DummyMappingEntity { @Id private Long id; // JPA实体必须包含@Id字段,无需对应数据库表 }
步骤3:调用存储过程
通过EntityManager执行存储过程并指定映射名称:
import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.StoredProcedureQuery; import java.util.List; @Repository public class EmployeeSalaryRepository { @PersistenceContext private EntityManager entityManager; public List<EmployeeSalaryDTO> fetchEmployeeSalaryData() { // 第一个参数是存储过程名称,第二个是结果集映射名称 StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "get_employee_multi_table_data", "EmployeeSalaryDTOMapping" ); // 若存储过程需要输入参数,添加以下代码: // query.registerStoredProcedureParameter("dept_id", Integer.class, ParameterMode.IN); // query.setParameter("dept_id", 10); return query.getResultList(); } }
方案2:Spring Data JPA Repository简化调用
基于方案1的映射,可直接在Repository接口中定义方法,通过@Procedure注解调用存储过程:
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.query.Procedure; import java.util.List; public interface DummyMappingRepository extends JpaRepository<DummyMappingEntity, Long> { // name属性对应存储过程名称,或用procedureName指定数据库中的存储过程名 @Procedure(name = "get_employee_multi_table_data") List<EmployeeSalaryDTO> getEmployeeSalaryData(); }
方案3:EntityManager原生SQL调用(灵活无注解)
如果觉得JPA注解过于繁琐,可直接用原生SQL调用存储过程,手动映射结果数组到DTO:
import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; @Repository public class EmployeeSalaryNativeRepository { @PersistenceContext private EntityManager entityManager; public List<EmployeeSalaryDTO> fetchEmployeeSalaryData() { String sql = "CALL get_employee_multi_table_data()"; Query query = entityManager.createNativeQuery(sql); // 添加参数:query.setParameter(1, "参数值"); List<Object[]> resultRows = query.getResultList(); return resultRows.stream() .map(row -> new EmployeeSalaryDTO( (String) row[0], (String) row[1], (BigDecimal) row[2] )) .collect(Collectors.toList()); } }
方案4:Spring JDBC Template(原生JDBC封装)
既然原生JDBC可实现,Spring JDBC Template提供了更简洁的封装,完全脱离JPA约束,性能更优:
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; @Repository public class EmployeeSalaryJdbcRepository { private final JdbcTemplate jdbcTemplate; // 构造函数注入JdbcTemplate public EmployeeSalaryJdbcRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public List<EmployeeSalaryDTO> fetchEmployeeSalaryData() { String sql = "CALL get_employee_multi_table_data()"; return jdbcTemplate.query(sql, this::mapResultSetToDTO); } private EmployeeSalaryDTO mapResultSetToDTO(ResultSet rs, int rowNum) throws SQLException { return new EmployeeSalaryDTO( rs.getString("emp_name"), rs.getString("dept_name"), rs.getBigDecimal("salary") ); } }
关键注意事项
- 存储过程返回的列名必须与映射中指定的
name完全匹配(大小写敏感与否取决于数据库配置) - DTO构造函数的参数类型必须与数据库返回的字段类型兼容
- 若存储过程返回数组格式结果,方案3和方案4可直接通过
Object[]或ResultSet处理 - 使用
@SqlResultSetMapping时,必须绑定到一个实体类(即使是空的占位类),这是JPA的设计要求
内容的提问来源于stack exchange,提问作者SYED SAQIB S A
相关产品推荐
相关产品推荐

