Spring Data JPA调用存储过程时触发ClassCastException异常求助
问题描述
在Spring Data JPA应用中,使用Jakarta EntityManager调用Oracle存储过程P_GET_ACTORS_BY_CATEGORIES时抛出java.lang.ClassCastException异常,异常栈如下:
java.lang.IllegalStateException: Failed to execute CommandLineRunner at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:777) ~[spring-boot-3.1.4.jar:3.1.4] at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:758) ~[spring-boot-3.1.4.jar:3.1.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) ~[spring-boot-3.1.4.jar:3.1.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309) ~[spring-boot-3.1.4.jar:3.1.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298) ~[spring-boot-3.1.4.jar:3.1.4] at com.example.bootdatajpaproj11callingprocedure.BootDataJpaProj11CallingProcedureApplication.main(BootDataJpaProj11CallingProcedureApplication.java:10) ~[classes/:na] Caused by: java.lang.ClassCastException: class org.hibernate.metamodel.model.domain.internal.EntityTypeImpl cannot be cast to class org.hibernate.query.OutputableType (org.hibernate.metamodel.model.domain.internal.EntityTypeImpl and org.hibernate.query.OutputableType are in unnamed module of loader 'app') at org.hibernate.procedure.internal.ProcedureParameterImpl.toJdbcParameterRegistration(ProcedureParameterImpl.java:118) ~[hibernate-core-6.2.9.Final.jar:6.2.9.Final] at org.hibernate.procedure.internal.StandardCallableStatementSupport.interpretCall(StandardCallableStatementSupport.java:90) ~[hibernate-core-6.2.9.Final.jar:6.2.9.Final] at org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs(ProcedureCallImpl.java:619) ~[hibernate-core-6.2.9.Final.jar:6.2.9.Final] at org.hibernate.procedure.internal.ProcedureCallImpl.getOutputs(ProcedureCallImpl.java:594) ~[hibernate-core-6.2.9.Final.jar:6.2.9.Final] at org.hibernate.procedure.internal.ProcedureCallImpl.outputs(ProcedureCallImpl.java:847) ~[hibernate-core-6.2.9.Final.jar:6.2.9.Final] at org.hibernate.procedure.internal.ProcedureCallImpl.getResultList(ProcedureCallImpl.java:973) ~[hibernate-core-6.2.9.Final.jar:6.2.9.Final] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:405) ~[spring-orm-6.0.12.jar:6.0.12] at jdk.proxy2/jdk.proxy2.$Proxy92.getResultList(Unknown Source) ~[na:na] at com.example.bootdatajpaproj11callingprocedure.runner.CallingPL_ProcedureTestRunner.run(CallingPL_ProcedureTestRunner.java:40) ~[classes/:na] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:774) ~[spring-boot-3.1.4.jar:3.1.4] ... 5 common frames omitted
相关代码与配置
1. Entity类Actor.java
//Actor.java (Entity class) package com.nt.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Data; @Entity @Data @AllArgsConstructor public class Actor { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer aid; @Column(length = 20) private String aname; @Column(length = 20) private String category; @Column(unique = true,nullable = true) private Long mobileNo; public Actor() { System.out.println("Actor: 0-param constructor"+this.getClass()); } }
2. Repository接口IActorRepository.java
//IActorRepository.java package com.nt.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.nt.entity.Actor; public interface IActorRepository extends JpaRepository<Actor, Integer> { }
3. 存储过程调用类CallingPL_ProcedureTestRunner.java
//CallingPL_ProcedureTestRunner.java package com.nt.runner; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import com.nt.entity.Actor; import jakarta.persistence.EntityManager; import jakarta.persistence.ParameterMode; import jakarta.persistence.StoredProcedureQuery; @Component public class CallingPL_ProcedureTestRunner implements CommandLineRunner { @Autowired private EntityManager manager; @Override public void run(String... args) throws Exception { StoredProcedureQuery query=manager.createStoredProcedureQuery("P_GET_ACTORS_BY_CATEGORIES", Actor.class); // register both IN ,OUT params of PL/SQL procedure query.registerStoredProcedureParameter(1, String.class,ParameterMode.IN); query.registerStoredProcedureParameter(2, String.class,ParameterMode.IN); //registering out param query.registerStoredProcedureParameter(3, Actor.class,ParameterMode.REF_CURSOR); //set values to IN params query.setParameter(1, "CLERK"); query.setParameter(2, "MANAGER"); //call PL/SQL procedure List<Actor> list=query.getResultList(); //proecess the result list.forEach(System.out::println); }//main }//class
4. Oracle表结构
CREATE TABLE "SYSTEM"."ACTOR" ( "AID" NUMBER(10,0) NOT NULL ENABLE, "ANAME" VARCHAR2(20 CHAR), "CATEGORY" VARCHAR2(20 CHAR), "MOBILE_NO" NUMBER(19,0), PRIMARY KEY ("AID"))
5. Oracle存储过程定义
create or replace NONEDITIONABLE PROCEDURE P_GET_ACTORS_BY_CATEGORIES ( ROLE1 IN VARCHAR2 , ROLE2 IN VARCHAR2 , DETAILS OUT SYS_REFCURSOR ) AS BEGIN open DETAILS for SELECT AID,ANAME,CATEGORY,MOBILE_NO FROM ACTOR WHERE CATEGORY IN(role1, role2) ORDER BY CATEGORY; END P_GET_ACTORS_BY_CATEGORIES;
6. application.properties配置
#DataSource Configuration (Bydefault hikaricp ) spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl spring.datasource.username=system spring.datasource.password=tiger #JPA- Hibenrate properties spring.jpa.database-platform=org.hibernate.dialect.OracleDialect spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update
环境信息
- Spring Boot 3.1.4
- Hibernate 6.2.9.Final
- Oracle数据库
解决方案
异常根源是注册REF_CURSOR类型输出参数时,错误指定Actor.class作为参数类型,Hibernate无法将实体类型转换为处理存储过程输出所需的OutputableType。
修改CallingPL_ProcedureTestRunner.java中的参数注册代码:
将注册REF_CURSOR参数的代码:
query.registerStoredProcedureParameter(3, Actor.class,ParameterMode.REF_CURSOR);
替换为:
query.registerStoredProcedureParameter(3, void.class, ParameterMode.REF_CURSOR);
修改后的完整run方法代码:
@Override public void run(String... args) throws Exception { StoredProcedureQuery query=manager.createStoredProcedureQuery("P_GET_ACTORS_BY_CATEGORIES", Actor.class); // register both IN ,OUT params of PL/SQL procedure query.registerStoredProcedureParameter(1, String.class,ParameterMode.IN); query.registerStoredProcedureParameter(2, String.class,ParameterMode.IN); //registering out param - 使用void.class替代Actor.class query.registerStoredProcedureParameter(3, void.class, ParameterMode.REF_CURSOR); //set values to IN params query.setParameter(1, "CLERK"); query.setParameter(2, "MANAGER"); //call PL/SQL procedure List<Actor> list=query.getResultList(); //proecess the result list.forEach(System.out::println); }
原理说明
- 调用
createStoredProcedureQuery时已指定结果映射的实体类型Actor.class,Hibernate会自动将REF_CURSOR返回的结果集映射到该实体。 - REF_CURSOR本身只是游标句柄,不需要指定为实体类,指定
void.class是告知Hibernate该参数为输出游标,无需额外类型转换。
内容的提问来源于stack exchange,提问作者PrateekAnand
相关产品推荐
相关产品推荐

