The stored procedure in question used an OUT parameter with a "cursor" type, this is how Oracle can "return" the results of a select statement. (I can feel my CS professor grading this now: "D-, NEVER use Out Params!" some languages like Java do not even have out params). And then the NEXT challenge: How do you call that from Java? Good question - this case doesn't happen in the abridged JDBC docs, nor in the Hibernate nor Spring frameworks docs either.
JDBC has standard support for ResultSets returned from a stored procedure CallableStatment, this is how many DB vendors like Postgres and SQLServer work. In those drivers, Statement.exeecuteQuery() returns a ResultSet. But Oracle has a long history of having backwards and low-compatibility issues for JDBC details,and this is another case of Oracle jamming their square-peg stored procedures into the round hole of JDBC. But you can make it work.
Here are the "challenges":
* Oracle Stored Procedures CANNOT return a result set (it's an old design?)
* Oracle "gives" you SYS_REFCURSOR on an OUT param (not a JDBC standard, so that's an odd feeling)
* Oracle's JDBC driver does not have a JDBC-standard way to do this, they require OracleType.CURSOR
* Oracle's driver rejects the "OTHER" SQL Type, so you cannot use this JDBC Standard either
Step 1: Your stored procedure
Oracle stored procedures can use an OUT param with type SYS_REFCURSOR to return results of a SQL Select:CREATE PROCEDURE "JAY_PROC" ( st_cursor OUT SYS_REFCURSOR ).... --details below
Step 2: CallableStatment
Of course, been there, done that - the ol' JDBC way to call stored procedures:CallableStatement cs = connection.prepareCall("call jay_proc(?)");
Step 3: register the OUT param
Feeling squeemish? It's OK... we are almost finished ...you'll feel a small prick in your good programming sense:cs.registerOutParameter(1, OracleType.CURSOR); cs.execute();
Step 4: blink... cast the ResultSet ??!
err.. Yes, it's true, the object returned is actually a ResultSet, but you have to cast it:ResultSet rs = (ResultSet) cs.getObject(1);
Step 5: Use the ResultSet as normal, JDBC calls
Whew! It's over.. Now we have what we wanted, a ResultSet:while( rs.next()){
Conclusion
Oracle does not directly support stored procedures that return ResultSet, but with these 3 or 4 steps, you can get the stored procedure with a cursor to work. I assume that Oracle's JDBC driver does the work of mapping their cursor object to a JDBC-compliant ResultSet, which is nice. But if I had a preference, I would wish for Oracle to support procedures that can return rows from a select. Then you could use the execute() method to return a ResultSet directly without the odd casting operation in step 4.Ideally you would use a SQL Select statement and the PreparedStatement JDBC object, but I was saddled with an existing stored procedure in this case. I do not recommend using stored procedures for almost any use case, if you can avoid it. Instead I prefer to use Java's standard JPA as much as possible.
Here is the working code:
package com.harpoontech.test.dao; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.springframework.jdbc.datasource.DriverManagerDataSource; public class TestOracleStoredProcedure { /** * The SQL TYPE for an Oracle CURSOR in a Oracle Stored Procedure. * This duplicates the OracleTypes.CURSOR, but with this constant * we do not need to import com.oracle.* jars into this project. * However this class is still 100% dependent on Oracle at runtime * and cannot be unit tested without Oracle. */ private int ORACLE_CURSOR_TYPE=-10; public static void main(String[] args) throws SQLException { TestOracleStoredProcedure t = new TestOracleStoredProcedure(); t.test(); } public void test() throws SQLException { //let Spring create a Data source for the Oracle connections DriverManagerDataSource ds = new DriverManagerDataSource(); String url = "jdbc:oracle:thin:@myhost:1525:DEVDB"; ds.setUrl(url); ds.setUsername("scott"); ds.setPassword("tiger"); CallableStatement cs = ds.getConnection() .prepareCall("call jay_proc(?)"); cs.registerOutParameter(1, ORACLE_CURSOR_TYPE); cs.execute(); ResultSet rs = (ResultSet) cs.getObject(1); while( rs.next()){ System.out.println("title:"+rs.getString(1)+" author:" +rs.getString(2)); } rs.close(); cs.close(); } /* --the Oracle 9+ stored proc with 1 OUT parameter for the results of a select: CREATE OR REPLACE PROCEDURE "JAY_PROC" ( st_cursor OUT SYS_REFCURSOR ) is BEGIN OPEN st_cursor FOR SELECT TITLE, AUTHOR, ISBN_ID FROM books; end JAY_PROC; */ }
Notes
* I used the Spring DriverManagerDataSource, but you could replace that with straight JDBC calls* Notice I used my own constant ORACLE_CURSOR_TYPE to avoid importing the oracle dependencies.
* You can pass the ResultSet object to another framework like Spring-JDBC, if you like
* I found most of this by using the debugger to show what Oracle drivers were really doing