如何处理@Entity上@NamedStoredProcedureQuery返回的2个自定义OUT类型
问题排查与解决方案
类型报错核心原因
JPA默认不支持直接将Oracle自定义类型(UDT)映射到自定义Java实体类,哪怕实现了SQLData接口也需要额外注册类型映射,直接在@StoredProcedureParameter的type字段填PersonalInfo.class/Financial.class会被JPA识别为非支持的JDBC类型,所以抛出Type cannot be null异常。
逐个解答疑问
- type字段配置规则
不需要把自定义POJO填到type字段,该字段仅接受JDBC标准类型,OUT参数的type统一填java.sql.Struct.class即可,后续手动做类型转换。 - schema配置规则
不需要给@NamedStoredProcedureQuery加单独的schema注解,直接在procedureName字段补全schema前缀即可,修改为JJR.pkg_employee_data.get_employee_data,否则会默认在当前连接用户的schema下查找存储过程。 - Struct.class不生效原因
仅设置type为Struct.class不够,还需要手动处理Struct到POJO的转换逻辑,@Procedure注解默认不会自动完成UDT到Java类的映射。 - Employee实体字段规则
标注@NamedStoredProcedureQuery的实体类不需要映射UDT里的所有字段,仅需保留必填的@Id字段即可,返回的UDT数据会存到Map<String, Object>结果里,后续单独转换即可,不需要堆在Employee类中。 - UDT继承base_t的影响
只要SQLData实现类的getSQLTypeName方法返回正确的子类型全限定名(带schema,全大写,比如JJR.PERSONAL_INFORMATION_T),且readSQL方法读取字段的顺序和Oracle中UDT定义的字段顺序完全一致(包括从base_t继承的字段),继承关系不会影响转换结果。
可行实现步骤
1. 调整@NamedStoredProcedureQuery配置
@Entity @NamedStoredProcedureQuery(name = "Employee.getEmployeeData", procedureName = "JJR.pkg_employee_data.get_employee_data", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "empid", type = Long.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "persinfo", type = Struct.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "financ", type = Struct.class)}) public class Employee { @Id private Long userId; }
2. 编写Struct转换工具类
public class UDTConverter { private static final Map<String, Class<?>> TYPE_MAP = Map.of( "JJR.PERSONAL_INFORMATION_T", PersonalInfo.class, "JJR.FINANCIAL_T", Financial.class ); @SuppressWarnings("unchecked") public static <T> T toObject(Struct struct) throws SQLException { if (struct == null) return null; return (T) struct.getObject(TYPE_MAP); } }
3. 调整业务层调用逻辑
@Service public class EmployeeService { @Autowired private EmployeeRepository employeeRepository; public EmployeeDataVO getEmployeeData(Long empId) throws SQLException { Map<String, Object> result = employeeRepository.getEmployee(empId); PersonalInfo personalInfo = UDTConverter.toObject((Struct) result.get("persinfo")); Financial financial = UDTConverter.toObject((Struct) result.get("financ")); return new EmployeeDataVO(personalInfo, financial); } }
注意事项
SQLData实现类的getSQLTypeName方法必须返回全大写、带schema的Oracle类型名,拼写错误会直接导致映射失败readSQL方法读取字段的顺序必须和Oracle UDT定义的字段顺序完全一致,包括从父类继承的字段,顺序错误会抛出类型转换异常- 若使用Hibernate作为JPA实现,也可以用Hibernate原生的
@Struct注解标注POJO类,无需手动实现SQLData接口
内容的提问来源于stack exchange,提问作者PainIsAMaster
相关产品推荐
相关产品推荐

