Spring Data JPA调用多out参数存储过程报Type cannot be null异常
问题现象
现有MySQL存储过程getProjectParams,接收1个int类型入参projectId,定义了companyName、projectNumber、projectOwner三个OUT参数,用于根据项目ID查询对应项目的公司名称、项目编号、项目负责人信息。
基于Spring Data JPA开发时,Repository层直接使用@Procedure注解定义了返回值为Map<String, Object>的getProjectParams(long projectId)方法,Service层调用该方法时抛出如下异常,无法正常获取三个输出参数:
Type cannot be null; nested exception is java.lang.IllegalArgumentException: Type cannot be null
报错根因
Spring Data JPA 不会自动识别多OUT参数存储过程的参数类型,也不支持直接将多OUT返回值自动映射为Map<String, Object>:如果没有显式声明所有OUT参数的名称、类型、参数模式,框架在处理返回值时找不到对应的类型映射规则,就会抛出类型为空的非法参数异常。
可落地的解决实现
提供两种稳定可用的实现方式,按需选择即可:
- 方式一:基于
@NamedStoredProcedureQuery+ 接口投影实现(适配Repository层直接调用的写法)- 首先在项目对应的
Project实体类上,显式声明存储过程的所有入参、出参配置,明确每个参数的模式、名称、类型:
- 首先在项目对应的
import jakarta.persistence.*; @Entity @NamedStoredProcedureQuery( name = "getProjectParams", procedureName = "getProjectParams", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "projectId", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "companyName", type = String.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "projectNumber", type = String.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "projectOwner", type = String.class) } ) public class Project { // 实体类自身的字段、方法省略 }
- 定义接口投影用来接收OUT参数返回值,接口内的get方法名必须和存储过程OUT参数名严格一致:
public interface ProjectParamResult { String getCompanyName(); String getProjectNumber(); String getProjectOwner(); }
- 调整Repository层的方法定义,绑定存储过程配置,返回值用上面定义的投影接口,入参加上
@Param注解绑定参数名:
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.query.Procedure; import org.springframework.data.repository.query.Param; public interface ProjectRepository extends JpaRepository<Project, Long> { @Procedure(name = "getProjectParams") ProjectParamResult getProjectParams(@Param("projectId") long projectId); }
后续Service层直接调用该Repository方法,就能从返回的ProjectParamResult对象中拿到三个OUT参数的值。
- 方式二:基于EntityManager原生调用存储过程(无需配置实体注解,灵活度更高)
如果不想在实体类上耦合存储过程配置,可以直接在Service层注入EntityManager,手动注册参数后调用,结果可直接封装为Map返回:
import jakarta.persistence.EntityManager; import jakarta.persistence.ParameterMode; import jakarta.persistence.StoredProcedureQuery; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @Service public class ProjectService { private final EntityManager entityManager; public ProjectService(EntityManager entityManager) { this.entityManager = entityManager; } public Map<String, Object> getProjectParams(long projectId) { StoredProcedureQuery spQuery = entityManager.createStoredProcedureQuery("getProjectParams"); // 注册入参 spQuery.registerStoredProcedureParameter("projectId", Integer.class, ParameterMode.IN); // 注册三个OUT参数,必须显式指定类型 spQuery.registerStoredProcedureParameter("companyName", String.class, ParameterMode.OUT); spQuery.registerStoredProcedureParameter("projectNumber", String.class, ParameterMode.OUT); spQuery.registerStoredProcedureParameter("projectOwner", String.class, ParameterMode.OUT); // 设置入参值 spQuery.setParameter("projectId", (int) projectId); // 执行存储过程 spQuery.execute(); // 提取OUT参数封装为Map Map<String, Object> resultMap = new HashMap<>(); resultMap.put("companyName", spQuery.getOutputParameterValue("companyName")); resultMap.put("projectNumber", spQuery.getOutputParameterValue("projectNumber")); resultMap.put("projectOwner", spQuery.getOutputParameterValue("projectOwner")); return resultMap; } }
注意事项
- 所有参数(包含IN、OUT)的名称必须和MySQL存储过程中定义的参数名完全一致,避免参数绑定失败。
- 参数的Java类型要和MySQL中定义的字段类型匹配,比如MySQL的
varchar/char类型对应Java的String,int类型对应Java的Integer,避免类型转换异常。 - 不要直接给
@Procedure标注的Repository方法设置Map<String, Object>作为返回值,Spring Data JPA默认没有提供对应类型的自动映射转换器,会触发类型识别失败。
内容的提问来源于stack exchange,提问作者Prasanth Vaindam
相关产品推荐
相关产品推荐

