Spring Data JPA调用Oracle包内存储过程报PLS-00201错误
问题描述
基于STS 4搭建Spring Boot应用对接Oracle 19c数据库,普通@Entity实体类映射表操作完全正常,调用Oracle包内定义的存储过程/函数时抛出PLS-00201: identifier 'LET_MOCK.SUMPROC' must be declared错误。
已确认目标包let_mock配置了同义词、授予了全部必要权限,已尝试加schema前缀、修改参数类型、替换包装类为基本类型等方案均无效,Hibernate生成的调用语句为{call let_mock.sumproc(?,?,?)},调用同包下函数也报相同错误。
涉及代码如下:
- Oracle包定义(仅提供了包体代码)
create or replace package body let_mock is function sumfun(p_a In Integer, p_b In Integer) return Integer Is Begin Return p_a + p_b; End; Procedure sumproc(p_a In Integer, p_b In Integer, po_res Out Integer) Is Begin po_res := p_a + p_b; End; end let_mock;
- 实体类存储过程配置
@Entity @NamedStoredProcedureQuery(name = "SumProc", procedureName = "let_mock.sumproc", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "p_a", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.IN, name = "p_b", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "po_res", type = Integer.class) }) @Table(name = TestataLetture.TABLE_NAME) public class TestataLetture { public static final String TABLE_NAME= "LET_TESTATE_LETTURE"; @Id @Column(name = "tele_testata_lettura_id") private String lettura_id; // 其余字段省略 }
- Repository接口定义
public interface RepoLetture extends JpaRepository<TestataLetture, String> { @Procedure(name = "SumProc") public int getSum(@Param("p_a") Integer a, @Param("p_b") Integer b); }
- 报错日志
Hibernate: {call let_mock.sumproc(?,?,?)} 2022-06-14 23:16:06.395 WARN 28180 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 6550, SQLState: 65000 2022-06-14 23:16:06.396 ERROR 28180 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : ORA-06550: row 1, column 7: PLS-00201: identifier 'LET_MOCK.SUMPROC' must be declared ORA-06550: row 1, column 7: PL/SQL: Statement ignored
根因说明
- 第一类常见原因:仅创建了包体(package body),未创建包规范(package specification),包内方法未对外暴露,外部连接用户无访问权限。
- 第二类核心原因:JPA/Hibernate默认的存储过程元数据查找逻辑,将Oracle的包(package)识别为catalog层级,直接把
包名.存储过程名写在procedureName属性中时,JDBC无法正确匹配到包内的存储过程对象;且Oracle默认元数据存储为全大写,小写对象名会直接匹配失败。
解决方案
按优先级从高到低尝试以下方案:
方案1:校验数据库侧包定义,优先排除权限/定义问题
- 首先确认已创建包规范(包头),将需要对外暴露的函数、存储过程声明在包规范中,参考代码:
-- 创建包规范,声明公开方法 create or replace package let_mock is function sumfun(p_a In Integer, p_b In Integer) return Integer; Procedure sumproc(p_a In Integer, p_b In Integer, po_res Out Integer); end let_mock; / -- 已有的包体代码无需修改 create or replace package body let_mock is function sumfun(p_a In Integer, p_b In Integer) return Integer Is Begin Return p_a + p_b; End; Procedure sumproc(p_a In Integer, p_b In Integer, po_res Out Integer) Is Begin po_res := p_a + p_b; End; end let_mock; / -- 给应用连接用户授予执行权限 grant execute on let_mock to <你的数据库连接用户名>;
- 用应用的连接账号直接在数据库客户端执行以下语句验证调用是否正常,排除数据库侧问题:
declare res integer; begin let_mock.sumproc(1,2,res); dbms_output.put_line(res); end; /
如果客户端执行也报相同错误,优先排查同义词、权限、包定义问题;如果客户端执行正常,再调整应用侧代码。
方案2:修正@NamedStoredProcedureQuery配置,适配Oracle包规则
JPA JDBC规范中,Oracle数据库的包对应catalog层级,不要把包名拼在存储过程名前,需要将包名配置在catalog属性中,且包名、存储过程名必须使用全大写(和Oracle元数据存储的大小写一致):
@Entity @NamedStoredProcedureQuery( name = "SumProc", procedureName = "SUMPROC", // 存储过程名,全大写 catalog = "LET_MOCK", // Oracle包名填在这里,全大写 parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "p_a", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.IN, name = "p_b", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.OUT, name = "po_res", type = Integer.class) } ) @Table(name = TestataLetture.TABLE_NAME) public class TestataLetture { public static final String TABLE_NAME= "LET_TESTATE_LETTURE"; @Id @Column(name = "tele_testata_lettura_id") private String lettura_id; // 其余实体字段省略 }
Repository层代码无需修改,保持原有@Procedure(name = "SumProc")的映射即可正常调用。
方案3:原生PL/SQL调用(兼容性最高)
如果上述JPA注解配置仍存在兼容问题,可以直接使用原生查询写标准PL/SQL调用语句,完全绕开Hibernate对存储过程的元数据解析逻辑,是生产环境最稳定的实现方式,不属于非规范hack:
- 存储过程调用写法:
import org.springframework.data.jpa.repository.query.OutParameter; public interface RepoLetture extends JpaRepository<TestataLetture, String> { @Query(value = "begin LET_MOCK.sumproc(:p_a, :p_b, :po_res); end;", nativeQuery = true) void getSum(@Param("p_a") Integer a, @Param("p_b") Integer b, @Param("po_res") OutParameter<Integer> result); }
调用示例:
OutParameter<Integer> sumResult = new OutParameter<>(Integer.class); repoLetture.getSum(1, 2, sumResult); // 取返回值 Integer res = sumResult.getValue();
- 包内函数调用写法:
@Query(value = "select LET_MOCK.sumfun(:p_a, :p_b) from dual", nativeQuery = true) Integer getSumFun(@Param("p_a") Integer a, @Param("p_b") Integer b);
内容的提问来源于stack exchange,提问作者Andrea Franceschini
相关产品推荐
相关产品推荐

