使用@NamedStoredProcedureQuery调用Oracle存储过程返回REF_CURSOR时,mode必须设为ParameterMode.REF_CURSOR且type=void.class,name需与存储过程参数名严格一致;调用须在@Transactional内执行execute()后用getResultList()获取结果,SELECT列名需显式别名为Java字段名以避免映射为null。

用 @NamedStoredProcedureQuery 声明存储过程时,mode 必须设为 ParameterMode.REF_CURSOR
Oracle 存储过程返回结果集只能靠 OUT SYS_REFCURSOR,JPA 不会自动识别它为结果集。如果在 @StoredProcedureParameter 中把游标参数的 mode 写成 IN 或漏掉,JPA 会当成普通 OUT 参数处理,导致 ResultSet 无法获取,甚至抛出 ORA-06550 或空结果。
正确写法示例:
@NamedStoredProcedureQuery(
name = "PersonRepository.findWithCursor",
procedureName = "MOVE_TO_HISTORY",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "person_id_in", type = Long.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "msg_out", type = String.class),
@StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, name = "o_cursor", type = void.class)
}
)
-
type = void.class是 JPA 对 REF_CURSOR 的固定写法,不能写ResultSet.class或Object.class - 必须显式指定
name,且与存储过程中参数名完全一致(大小写敏感) - 若存储过程有多个
OUT SYS_REFCURSOR,每个都得单独声明一个@StoredProcedureParameter,JPA 不支持多游标自动映射
调用时要用 StoredProcedureQuery.execute(),不能用 getSingleResult()
很多人看到返回结果就直觉调 getSingleResult(),但 REF_CURSOR 返回的是 ResultSet,不是单个实体对象。直接调这个方法会触发类型转换失败,报 IllegalArgumentException: Result has more than one element 或空指针。
正确流程是:
- 先
execute() - 再用
getResultList()拿 List,或getSingleResult()仅当确定只有一行时才用(不推荐) - 如果要手动遍历,得用
getResultStream()或getResultList().forEach(...)
注意:JPA 的 getResultList() 底层会调用 JDBC 的 rs.next(),所以存储过程里 OPEN o_cursor FOR ... 后不能有任何 CLOSE 或提前 RETURN,否则游标未打开就结束,Java 侧拿到的就是 Cursor is closed。
实体类字段名必须和查询列名严格匹配,别指望自动下划线转驼峰
Oracle 存储过程里 SELECT 的列名默认是大写(如 ID, FIRST_NAME),而 JPA 默认按 Java 字段名(id, firstName)去映射。如果不做适配,所有字段都会是 null。
- 方案一:在存储过程中用别名,比如
SELECT id AS "id", first_name AS "firstName" FROM person - 方案二:在实体类字段上加
@Column(name = "FIRST_NAME"),但注意 name 值要跟实际 SQL 列名一致(含大小写) - 方案三:全局配置
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy,但它对存储过程返回列无效——只影响 JPA 自动生成的 SQL
最稳妥的做法是第一种:在存储过程的 SELECT 里显式 alias 成小写或驼峰形式,避免依赖框架猜测。
事务边界没包住存储过程调用,会导致游标在读取前被关闭
JPA 调用存储过程时,如果方法没加 @Transactional,Spring 可能在 execute() 后立即释放数据库连接,而你还在后续代码里调 getResultList() ——这时游标句柄已失效,报错就是 Cursor is closed。
- 必须确保整个调用链(从
createStoredProcedureQuery到getResultList())都在同一个事务内 - 如果用
EntityManager手动创建,别用entityManager.unwrap(Session.class)开新 Session,那会脱离 Spring 管理的事务上下文 - 推荐方式:在 service 层方法上加
@Transactional(readOnly = true),然后直接注入EntityManager调用
容易忽略的一点:即使存储过程本身只读,JPA 仍需要事务来维持连接生命周期。没有事务,连接可能在 ResultSet 构建完成前就被归还给连接池。











