Oracle存储过程必须用OUT SYS_REF CURSOR返回结果集,不可直接SELECT或RETURN;JDBC需注册Types.OTHER,MyBatis须用XML+CALLABLE+resultMap,且注意事务与连接释放。
Oracle存储过程必须用REF CURSOR返回结果集
oracle不支持直接在存储过程中用select语句返回行集,必须声明out类型的ref cursor参数,由调用方打开并读取。常见错误是写成return sys_refcursor或试图在过程体里return cursor_var——这在pl/sql中语法非法。
正确写法示例:
CREATE OR REPLACE PROCEDURE P_FENYE(
p_page_no IN NUMBER,
p_page_size IN NUMBER,
p_result OUT SYS_REFCURSOR
) AS
v_offset NUMBER := (p_page_no - 1) * p_page_size;
BEGIN
OPEN p_result FOR
SELECT * FROM (
SELECT a.*, ROWNUM rnum FROM (
SELECT id, name, create_time FROM player ORDER BY id
) a WHERE ROWNUM v_offset;
END;
-
p_result必须是OUT SYS_REFCURSOR,不能是IN OUT或IN - 不要在过程里执行
FETCH或CLOSE,那是JDBC或MyBatis的事 - 如果用
ROW_NUMBER() OVER替代嵌套ROWNUM,注意Oracle 12c+才完全支持OFFSET ... FETCH,老版本仍需嵌套写法
Spring Boot里用JdbcTemplate调用带REF CURSOR的存储过程
JdbcTemplate对REF CURSOR有原生支持,但必须用SqlOutParameter显式注册类型为Types.OTHER,否则会报ORA-06550: PLS-00306参数类型不匹配。
关键代码片段:
@Autowired
private JdbcTemplate jdbcTemplate;
<p>public List<map object>> callFenYe(int page, int size) {
String sql = "{call P_FENYE(?, ?, ?)}";
Map<string object> result = new JdbcCall(jdbcTemplate)
.withProcedureName("P_FENYE")
.declareParameters(
new SqlParameter("p_page_no", Types.NUMERIC),
new SqlParameter("p_page_size", Types.NUMERIC),
new SqlOutParameter("p_result", Types.OTHER) // 必须是Types.OTHER
)
.execute(Map.of("p_page_no", page, "p_page_size", size));</string></map></p><pre class="brush:php;toolbar:false;">return (List<map object>>) result.get("p_result");</map>
}
- 别用
queryForObject或query直接调用{call ...},它们不处理游标输出 -
SqlOutParameter的name必须和存储过程中OUT参数名完全一致(大小写敏感) - 返回的
List里字段名默认全大写,因Oracle元数据返回的是大写列名
MyBatis Plus不支持REF CURSOR,改用Mapper XML手动定义
MyBatisPlusInterceptor的PaginationInnerInterceptor只拦截SELECT语句,对存储过程调用无感知;@Select("CALL ...")也不能解析游标。必须退回到XML Mapper方式,并在中用statementType="CALLABLE"。
Mapper XML示例(PlayerMapper.xml):
<select id="selectByProcedure" statementtype="CALLABLE" resulttype="Player">
{CALL P_FENYE(
#{pageNo,jdbcType=NUMERIC},
#{pageSize,jdbcType=NUMERIC},
#{result,mode=OUT,jdbcType=OTHER,javaType=java.sql.ResultSet,resultMap=playerResultMap}
)}
</select>
-
resultMap必须提前定义好,不能靠自动映射,因为游标返回的列结构不固定 -
#{result,...}里的mode=OUT和jdbcType=OTHER缺一不可 - Java接口方法不能用
@Select注解,必须配XML,且方法返回值要是List而非Page——分页逻辑已由存储过程完成
容易被忽略的事务与连接泄漏风险
存储过程内部若含DML操作(如分页同时更新统计表),而调用方未开启事务,会导致一致性破坏;更隐蔽的是,REF CURSOR底层依赖数据库连接,若JDBC未正确关闭ResultSet,连接池可能耗尽。
- 确保调用方方法加
@Transactional,尤其当存储过程内有UPDATE/INSERT - 用
JdbcTemplate时,它会自动关闭游标;但若手写Connection+CallableStatement,必须在finally块中rs.close(); stmt.close(); conn.close(); - MyBatis里若XML中漏写
resultMap,异常堆栈可能只报NullPointerException,实际是ResultSet未被映射就释放了











