Errors when fetching sys cursor from plsql using jdbc - java

I have a stored procedure which returns sys_refcursor and I am trying to fetch the cursor from java using JDBC.
plsql stored procedure
create or replace procedure my_proc(p_deptno IN number,p_emp_no IN varchar2
, p_cursor OUT SYS_REFCURSOR)
is
begin
open p_cursor FOR
select *
from emp
where deptno = p_deptno and emp_number=p_emp_no;
end proc;
/
java code
callablestatement = connection.prepareCall("{cal my_proc(?,?,?)} ");
callablestatement.setInt(1, param1);
callablestatement.setString(2, param2);
callablestatement.registerOutParameter(3, OracleTypes.CURSOR);
callablestatement.execute();
resultSet = ((OracleCallableStatement)callablestatement).getCursor(4);
while (resultSet.next()) {
<classname> = mapList(resultSet);
logger.info(resultSet.getString(1));
}
When I execute the above I am getting the following execeptions
java.lang.NullPointerException at callablestatement.execute();
and
Non supported SQL92 token at position: 3: cal

You have a typo in your statement:
{cal my_proc(?,?,?)}
should be
{call my_proc(?,?,?)}

use theCallableStatement.setObject( instead
Of
.setString and .setInt etc
it has also theCallableStatement.setNull for null
but keep registerOutParameter the same
description for the method
Sets the value of the designated parameter with the given object. The
second parameter must be of type Object; therefore, the java.lang
equivalent objects should be used for built-in types. The JDBC
specification specifies a standard mapping from Java Object types to
SQL types. The given argument will be converted to the corresponding
SQL type before being sent to the database. Note that this method may
be used to pass datatabase- specific abstract data types, by using a
driver-specific Java type. If the object is of a class implementing
the interface SQLData, the JDBC driver should call the method
SQLData.writeSQL to write it to the SQL data stream. If, on the other
hand, the object is of a class implementing Ref, Blob, Clob, NClob,
Struct, java.net.URL, or Array, the driver should pass it to the
database as a value of the corresponding SQL type. This method throws
an exception if there is an ambiguity, for example, if the object is
of a class implementing more than one of the interfaces named above.
Note: Not all databases allow for a non-typed Null to be sent to the
backend. For maximum portability, the setNull or the setObject(String
parameterName, Object x, int sqlType) method should be used instead of
setObject(String parameterName, Object x).

I have resolved the issue by using a function with the same code as in the procedure and it has resolved my issue.
And I called my function using the following manner.
private final String FUNCTIONAME= "begin ? :=myfunc(?,?,?); end;"";
Thanks

Related

How to pass an array of Java strings as CLOBS to an oracle stored procedure that takes an array of CLOBS

I have an Oracle stored procedure that takes an array of clobs that need to be invoked from Java using JDBC. I have the data as a Set in my java code.
Tried several different approaches and nothing seems to work. Anyone has sample code to do this, please post.
Developer of the stored procedure has defined a custom data type called "CLOB_ARRAY" which is a TABLE of CLOBS.
When it is an array of VARCHAR it works fine.
I found a work around using Oracle Type and Struct. Below is a summary of the solution.
STEP 1:
Create a type as below - this has to be done at the database level using SQL Developer or SQL Plus. Not within the package
create or replace TYPE TYPE_DTAP_RECORD_STORE AS OBJECT( DATA_STORE_ID VARCHAR2(300), INDEX_RECORD CLOB);
STEP 2 :
In the package define an array of the above type
TYPE RECORD_ARRAY IS TABLE OF TYPE_DTAP_RECORD_STORE INDEX BY BINARY_INTEGER
STEP 3 : Create the stored procedure as below
procedure baseline_record_insert_bulk (i_record in record_array);
STEP 4:
In Java, write a DAO method as below to call the stored procedure
public void bulkAddToRecordStore(Map<String,String> jsonArray) throws SQLException {
List<Object>recordList = new ArrayList<>();
OracleConnection oraConnection = getConnection();
OracleCallableStatement cs =(OracleCallableStatement) getConnection().prepareCall("BEGIN SCHEMA.PACKAGE.baseline_record_insert_bulk(?, ?); END;")
for(String key :jsonArray.keySet()){
Clob clob=oraConnection.createClob();
clob.setString(1, jsonArray.get(key));
Struct rec=oraConnection.createStruct("SCHEMA.TYPE_DTAP_RECORD_STORE", new Object[]{key,clob});
recordList.add(rec);
}
Array sqlArray = oraConnection.createOracleArray("SCHEMA.PACKAGE.RECORD_ARRAY", recordList.toArray());
cs.setObject(1, sqlArray);
cs.execute();
sqlArray.free();
cs.close();
}

Passing object types to Oracle stored procedure gives "Invalid name pattern" error

Oracle Database administrator provided me a stored procedure. It takes (IN) varchar and supplies RECORD type. Record type is as follows:
TYPE BAL_RECORD_TYPE IS RECORD
(
ac_no VARCHAR2 (50),
Account_type VARCHAR2 (50),
ac_status VARCHAR2 (10),
cur_code VARCHAR2 (5),
available_balance NUMBER
);
I've followed the link below to support RECORD type in my jdbc application.
http://betteratoracle.com/posts/31-passing-record-types-between-oracle-and-java
From above article I've changed
stmt.registerOutParameter(2, OracleTypes.STRUCT, "RECTYPE");
to
stmt.registerOutParameter(2, OracleTypes.STRUCT, "BAL_RECORD_TYPE");
as BAL_RECORD_TYPE is my RECORD name.
But I'm getting the following error:
invalid name pattern: APPADMIN.BAL_RECORD_TYPE
Here APPADMIN is username of the database. I don't know how is it binding with OUT parameter.
My questions are:
Is there any good example code that I can follow to support RECORD type of oracle?
What is the possible reason of the error (invalid name pattern: APPADMIN.BAL_RECORD_TYPE)?
Addition:
How difficult would it be to implement CURSOR type instead of RECORD type for my oracle database administrator?
I've found the issue. Actually, it was not in my part. Our oracle DB administrator defined type inside package which is not accessible from JAVA.
Here is the excerpt:
We can't call a type which is defined inside a package in ORACLE. Java can't access this type, but sqlplus can access. So, our DB Administrator finally defined the type outside of the package.
I've found this solution from here:
http://forum.spring.io/forum/spring-projects/data/34668-array-as-out-parameter-java-sql-sqlexception-ora-06550-line-1-column-7
Unfortunately, you cannot pass a PL/SQL RECORD type to a stored procedure, or fetch it from a stored procedure with JDBC directly. But you can use this trick here to work around this limitation:
DELARE
rec APPADMIN.BAL_RECORD_TYPE;
BEGIN
my_proc(some_input, rec);
? := rec.ac_no;
? := rec.account_type;
? := rec.ac_status;
? := rec.cur_code;
? := rec.available_balance;
END;
You can now call your procedure as follows:
try (CallableStatement s = con.prepareCall(" ... above PL/SQL code ...")) {
// Register each of your record's individual attribute types:
s.registerOutParameter(1, Types.VARCHAR);
s.registerOutParameter(2, Types.VARCHAR);
s.registerOutParameter(3, Types.VARCHAR);
s.registerOutParameter(4, Types.VARCHAR);
s.registerOutParameter(5, Types.NUMERIC);
s.execute();
// Fetch each of your record's individual attribute types:
String acNo = s.getString(1);
String accountType = s.getString(1);
String acStatus = s.getString(1);
String curCode = s.getString(1);
BigDecimal availableBalance = s.getBigDecimal(1);
}
I've recently blogged about this technique more in detail. The blog post also contains further info about how to automate this technique if you have many procedures / functions taking RECORD type IN/OUT parameters.

Calling PL/SQL procedure with SYS_REFCURSOR as IN parameter using JDBC

I am trying to understand how I can call a PL/SQL procedure which takes a SYS_REFCURSOR as IN parameter.
Consider the following PL/SQL procedure:
print_cursor_contents(myCursor SYS_REFCURSOR , row_count OUT NUMBER);
At the time of binding value to the IN parameter which setXXX method do I use ?
To me a java Class with individual cursor record fields , as it members and a Array of instances of this class seems the proper way to represent a plsql CURSOR. I get a SQLException when I do this:
I used the following set method
callStmt.setObject(1, curRec);
Here is the exception I got for using the above statement:
Exception occured in the database
Exception message: Invalid column type
java.sql.SQLException: Invalid column type
at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:8921)
at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:8396)
at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:9176)
at oracle.jdbc.driver.OracleCallableStatement.setObject(OracleCallableStatement.java:5024)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:234)
at com.rolta.HrManager.printMaxSalAllDept(HrManager.java:1022)
at com.rolta.HrManager.main(HrManager.java:1116)
Database error code: 17004
To me a java Class with individual cursor record fields , as it members and a Array of instances of this class seems the proper way to represent a plsql CURSOR.
I disagree.
If you have a stored function or procedure that either returns a ref cursor or has a ref cursor as an OUT parameter, the ref cursor comes out of JDBC as a ResultSet. So, if it were possible to call a stored procedure with a SYS_REFCURSOR parameter, I'd suspect that a ResultSet would be what you would need to pass.
In fact, my suspicions are confirmed. If you take a look at Oracle's extension to CallableStatement, OracleCallableStatement, it inherits a setCursor(int, ResultSet) method from its superinterface OraclePreparedStatement. Therefore, you could cast the CallableStatement to OracleCallableStatement, call the setCursor method, and away you go.
Except this approach doesn't actually work.
If you try calling setCursor on an OracleCallableStatement, you will get an exception java.sql.SQLException: Unsupported feature.
You can try callingsetObject with a ResultSet, but you will only get another java.sql.SQLException: Invalid column type exception.
Here's a test class you can run to verify either case. It calls one stored procedure to get a ref cursor (and hence a ResultSet) and then tries to pass it to the other:
import java.sql.*;
import oracle.jdbc.OracleTypes;
import oracle.jdbc.OracleCallableStatement;
public class JavaRefCursorTest {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:#localhost:1521:XE", "user", "password");
try (CallableStatement cstmt1 = conn.prepareCall(
"{ call java_ref_curs_test.get_ref_cursor(?)}")) {
cstmt1.registerOutParameter(1, OracleTypes.CURSOR);
cstmt1.execute();
try (ResultSet rSet = (ResultSet)cstmt1.getObject(1)) {
try (CallableStatement cstmt2 = conn.prepareCall(
"{ call java_ref_curs_test.print_refcursor(?)}")) {
// Uncomment the next line to call setCursor:
// ((OracleCallableStatement)cstmt2).setCursor(1, rSet);
// Uncomment the next line to call setObject:
// cstmt2.setObject(1, rSet);
cstmt2.execute();
}
}
}
}
}
(The two procedures in the java_ref_curs_test take a single SYS_REFCURSOR parameter: get_ref_cursor returns a ref cursor and print_refcursor takes one as a parameter but does nothing with it.)
So, which setXXX method should you use? I would say none of them. What you are asking for is not possible directly.
It may still be possible to call this procedure, but you will have to create the ref cursor in PL/SQL, not in Java, and then pass it to your procedure.
For example, I could use the following PL/SQL block to call the two procedures used in the above example:
DECLARE
l_curs SYS_REFCURSOR;
BEGIN
java_ref_curs_test.get_ref_cursor(l_curs);
java_ref_curs_test.print_refcursor(l_curs);
END;
You can fairly easily run this from JDBC: put it in a string and pass it to Statement.executeUpdate().

Accessing oracle data type from java

i want to access a data type defined in a package from java when executing function.Please help me.
Package and function:
create or replace package types as
TYPE name_array IS VARRAY(100000) OF VARCHAR2(200);
end types;
create or replace function test_pack1 return types.name_array as
names types.name_array := types.name_array();
begin
for emp in (Select state from test where test_ID BETWEEN 1 AND 120000) loop
names.extend;
names(names.count) := emp.state;
end loop;
return names;
end test_pack1;
java code
cstmt = (OracleCallableStatement) con.prepareCall("begin ? :=test_pack1; end;");
cstmt.registerOutParameter(1, OracleTypes.ARRAY,"NAME_ARRAY");
cstmt.execute();
In the above i am getting error at the second line.
Err msg below:
java.sql.SQLException: invalid name pattern: xyz.NAME_ARRAY
function is getting executed in Oracle successfully.But when we try execute from java its throwing above exception.
Newer versions of Oracle (since 12c Release 1) claim to support types inside of packages (so they can be accessed via JDBC).
All PL/SQL package types are mapped to a system-wide unique name that can be used by JDBC to retrieve the server-side type metadata. The name is in the following form:
[SCHEMA.]<PACKAGE>.<TYPE>
from here: http://docs.oracle.com/cd/E16655_01/java.121/e17657/apxref.htm#CHEIIJCC
You have to use SQL type for this purpose, not PL/SQL type, so you need to define the type outside of PL/SQL (i.e. on schema level, not package level):
CREATE OR REPLACE TYPE NAME_ARRAY AS VARRAY(100000) OF VARCHAR2(200);
and use it in your function
create or replace function test_pack1 return name_array as
names name_array := name_array();
...
For this to work it is not possible for the array type to be defined in a package, as much as it is convenient to do so. Oracle JDBC driver is not able to "see" package types in this manner (this applies not only to arrays, but to all custom defined types you may want to return via a JDBC call).
You need to define the array as a schema level type instead, after which this sort of code should be able to work fine.

Calling an Oracle PL/SQL procedure with Custom Object return types from 0jdbc6 JDBCthin drivers

I'm writing some JDBC code which calls a Oracle 11g PL/SQL procdedure which has a Custom Object return type. Whenever I try an register my return types, I get either ORA-03115 or PLS-00306 as an error when the statement is executed depending on the type I set. An example is below:
PLSQL Code:
Procedure GetDataSummary (p_my_key IN KEYS.MY_KEY%TYPE,
p_recordset OUT data_summary_tab,
p_status OUT VARCHAR2);
More PLSQL Code (Custom Object Details):
CREATE OR REPLACE TYPE data_summary_obj
AS
OBJECT (data_key NUMBER,
data_category VARCHAR2 (100),
sensitive_flag VARCHAR2 (1),
date_created DATE,
date_rep_received DATE,
date_first_offering DATE,
agency_data_ref VARCHAR2 (13),
change_code VARCHAR2 (120),
data_ref VARCHAR2 (50),
data_status VARCHAR2 (100),
data_count NUMBER)
/
CREATE OR REPLACE TYPE data_summary_tab AS TABLE OF data_summary_obj
/
Java Code:
String query = "begin manageroleviewdata.getdatasummary(?, ?, ?); end;");
CallableStatement stmt = conn.prepareCall(query);
stmt.setInt(1, 83);
stmt.registerOutParameter(2, OracleTypes.CURSOR); // Causes error: PLS-00306
stmt.registerOutParameter(3, OracleTypes.VARCHAR);
stmt.execute(stmt); // Error mentioned above thrown here.
Can anyone provide me with an example showing how I can do this? I guess it's possible. However I can't see a rowset OracleType. CURSOR, REF, DATALINK, and more fail.
Apologies if this is a dumb question. I'm not a PL/SQL expert and may have used the wrong terminology in some areas of my question. (If so, please edit me).
Thanks in advance.
Regs, Andrew
I finally (with a little help from others) found out the answer to this. It came in three parts:
The first was that I needed to use an:
OracleCallableStatement stmt = (OracleCallableStatement) conn.prepareCall(query);
rather than the simple JDBC CallableStatement I had been trying to use.
The second part was that I had to register my "out" parameter as follows:
stmt.registerOutParameter(2, OracleTypes.STRUCT, "DATA_SUMMARY_TAB");
The third part, and it is implicit in part 2 above, was that "DATA_SUMMARY_TAB" had to be in UPPER CASE. If you put it in lower case, then you get a cryptic error message as follows:
java.sql.SQLException: invalid name pattern: MYTEST.data_summary_tab
at oracle.jdbc.oracore.OracleTypeADT.initMetadata(OracleTypeADT.java:553)
at oracle.jdbc.oracore.OracleTypeADT.init(OracleTypeADT.java:469)
at oracle.sql.StructDescriptor.initPickler(StructDescriptor.java:390)
at oracle.sql.StructDescriptor.(StructDescriptor.java:320)
That's it.
Also, please note our custom object type was not in any packages. If it is, you might need to hack the third parameter around a little.
You two different and perhaps contradictory error messages there.
PLS-00306: wrong number or types of arguments in call to 'string'
What is the dexcription of user defined type data_summary_tab? OracleTypes.CURSOR expects a REF CURSOR, which is equivalent to a JDBC ResultSet. Whereas data_summary_tab sounds like it might be a varray or nested table.
ORA-03115: unsupported network datatype or representation
This suggests you are using an older version of the client than the database server (say 10g or even 9i). Normally we can get away with it, but sometime it can cause bugs where we're doing uncommon things. I'm not sure whether calling user-defined types over JDBC ought to count as an "uncommon thing" but I suspect it may.

Categories