Friday, December 28, 2007

Passing pl/sql table to Java using Oracle JDBC and vice - versa

While replying several threads on OA Forums(http://forums.oracle.com/forums/forum.jspa?forumID=210), I have seen couple of threads where developers have a requirement to pass a pl/sql table to OA Framework Page and vice-versa.Even if people copy paste this code from google. oracle or other available sources, they are not sure what actually they are doing!
Actually the best practice for such a requirement is to use Rosetta to generate class file for the particular pl/sql pkg.But the point is Rosetta is Oracle's internal tool and Oracle does not ship it to customers.Then also,I have seen it to be used in consulting industry, very often, i don't know how :)!But if u don't have rosetta, u don't have an option that to go with jdbc, which is anyways base of Oracle and Java bridge.When talking of JDBC, there is two set of JDBC classes popular in market, one from SUN(Founder of Java)and Oracle.Oracle jdbc wrapper classes are better in the sense, that they are flexible enough to cater almost all pl/sql objects.
Ok, so if you are not using Rosetta, then how would u pass pl/sql table to Java using Oracle JDBC and vice - versa?Recently was helping a friend in this code, had to some struggle for this code , so thought to put it on my blog, so that would help other developers with similar requirement.

So, lets start:
Read comments carefully to understand the code

1)Define Object with the same structure as your table of records u need to use in ur pl/sql procedureof some pkg.Remember define it as global object type and not inside package specification, because in that case our jdbc code would not be to find this object type.
/******************************************************************************/
/*The Script used to create Object type in this example is as follows:*******/
/******************************************************************************/
create or replace type rectype as object(col1 varchar2(10),col2 Date,col3 Number);



2)Define table of object u have defined above which u need to use in ur pl/sql procedure of some pkg.Remember define it as global table type and not inside package specification, because in that case our jdbc code would not be to find this object type.
/******************************************************************************/
/*The Script used to create table of Object type is as follows:*******/
/******************************************************************************/
create or replace type rectab as table of rectype;


3)Defining package specification and procedure definition
/*********************************************************************************/
/*The Script used to create package specification in this eg.is as follows:*******/
/******************************************************************************/

create or replace package ioStructArray as
procedure testproc(iorec in rectab,orec out rectab);
end ioStructArray;
/



4)Defining package body and procedure
/*********************************************************************************/
/*The Script used to create package specification in this eg.is as follows:*******/
/******************************************************************************/
create or replace package body ioStructArray as
procedure testproc(iorec in rectab,orec out rectab) is
begin
/*see how to loop and assign values*/
for i in 1..iorec.count loop
iorec(i).col1 := orec(i).col2;
iorec(i).col2 := orec(i).col1;
end loop;
end testproc;
end ioStructArray;
/

5)Getting connection object in JDBC Code :
//Getting db connection in a jdbc
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
// Connect to the database
Connection conn=DriverManager.getConnection ("jdbc:oracle:oci8:@S692815.WORLD",
"scott", "tiger");



6)If writing in AM in OAF page, code to get connection:
OADBTransactionImpl oadbtransactionimpl = (OADBTransactionImpl)getOADBTransaction();
OracleConnection conn = (OracleConnection)oadbtransactionimpl.getJdbcConnection();

7)Lets, consider a simple scenario where we have a XXVO in AM and I have to pass all VO rows data to the pl/sql procedure we just created and then receieve a table of data back and then based on it values do something in AM.Here is the Code:
//imports
import oracle.sql.*;
import oracle.jdbc.driver.OracleConnection;
import oracle.jdbc.driver.OracleCallableStatement;
import oracle.apps.fnd.framework.server.OADBTransaction;
import oracle.apps.fnd.framework.server.OADBTransactionImpl;

Public void arryToPLSQL()
{
//Getting Db connection
OADBTransactionImpl oadbtransactionimpl = (OADBTransactionImpl)getOADBTransaction();
OracleConnection conn = (OracleConnection)oadbtransactionimpl.getJdbcConnection();

//Defining variables

//oracle.sql.ARRAY we will use as out parameter from the package
//and will store pl/sql table
ARRAY message_display = null;

//ArrayList to store object of type struct
ArrayList arow= new ArrayList();

//StructDescriptor >> use to describe pl/sql object
//type in java.
StructDescriptor voRowStruct = null;

//ArrayDescriptor >> Use to describe pl/sql table
//as Array of objects in java
ArrayDescriptor arrydesc = null;

//Input array to pl/sql procedure
ARRAY p_message_list = null;

//Oracle callable statement used to execute procedure
OracleCallableStatement cStmt=null;

try
{
//initializing object types in java.
voRowStruct = StructDescriptor.createDescriptor("RECTYPE",conn);
arrydesc = ArrayDescriptor.createDescriptor("RECTAB",conn);
}

catch (Exception e)
{
throw OAException.wrapperException(e);
}

for(XXVORowImpl row = (XXVORowImpl)XXVO.first();
row!=null;
row = (XXVORowImpl)XXVO.next())
{
//We have made this method to create struct arraylist
// from which we will make ARRAY
//the reason being in java ARRAY length cannot be dynamic
//see the method defination below.
populateObjectArraylist(row,voRowStruct,arow);
}

//make array from arraylist
STRUCT [] obRows= new STRUCT[arow.size()];
for(int i=0;i < arow.size();i++)
{
obRows[i]=(STRUCT)arow.get(i);
}

try
{
p_message_list = new ARRAY(arrydesc,conn,obRows);
}
catch (Exception e)
{
throw OAException.wrapperException(e);
}

//jdbc code to execute pl/sql procedure
try
{
cStmt
=(OracleCallableStatement)conn.prepareCall("{CALL ioStructArray.testproc(:1,:2)}");
cStmt.setArray(1,p_message_list);
cStmt.registerOutParameter(2,OracleTypes.ARRAY,"RECTAB");
cStmt.execute();

//getting Array back
message_display = cStmt.getARRAY(2);
//Getting sql data types in oracle.sql.datum array
//which will typecast the object types
Datum[] arrMessage = message_display.getOracleArray();

//getting data and printing it
for (int i = 0; i < arrMessage.length; i++)
{
oracle.sql.STRUCT os = (oracle.sql.STRUCT)arrMessage[i];
Object[] a = os.getAttributes();
System.out.println("a [0 ] >>attribute1=" + a[0]);
System.out.println("a [1 ] >>attribute2=" + a[1]);
System.out.println("a [2 ] >>attribute3=" + a[2]);
//You can typecast back these objects to java object type


}

}
catch (Exception e1)
{
throw OAException.wrapperException(e1);
}
}



/*Our custom method which will populate
arraylist with struct object type
*/
public void populateObjectArraylist( XXVORowImpl row,StructDescriptor voRowStruct , ArrayList arow)
{
Object[] attribMessage = new Object[3];
String attr1 = null;
Date attr2 = null;
Number attr3 = null;

//Get value from Vo row and put in attr1,att2 and attr 3

//Putting values in object array
attribMessage[0]=attr1;
attribMessage[1]=attr2;
attribMessage[2]=attr3;

try
{
STRUCT loadedStructTime = new STRUCT(voRowStruct, conn, attribMessage);
arow.add(loadedStructTime);
}
catch (Exception e)
{
}

}

So, i hope every step is clear in the above code. I hope it helps all OAF developer community.Special Thanks to Pooja Arora for her contribution and constantly bugging me to fix this!

7 comments:

Asra said...

Hi Mukul,

My requirement is to call a pl/sql procedure by passing a record type variable. The structure of the procedure is a follows :

customer_details (
p_customer_record IN OUT xxff_customer_details%ROWTYPE,
p_err_status OUT VARCHAR2,
p_err_message OUT VARCHAR2)

How can I pass the data in the first parameter and also retrieve the data after the call as it is IN OUT param.

OCA-Z1007 said...

Hi,

I have a issue with the rosetta, this is the following code.
oracle.apps.okl.bc4jrosetta.OklDealCreatPvt.DealValuesRec adealvaluesrec[] = new oracle.apps.okl.bc4jrosetta.OklDealCreatPvt.DealValuesRec[1];
OklDealCreatPvt.loadDeal(oadbtransaction, number, s1, as, anumber, as1, number1, adealvaluesrec);

if(as[0] != null && !"S".equals(as[0]) && anumber[0] != null && as[0] != null && as1[0] != null)
OAExceptionUtils.checkErrors(oadbtransaction, anumber[0].intValue(), as[0], as1[0]);

and the rosetta is returning a unexpected error how do i need to debug the OklDealCreatPvt.class....

can you please help...

Jaiwardhan said...

Hi,

I Think following piece of code has typo error:-

//make array from arraylist
STRUCT [] obRows= new STRUCT[arow.size()];
for(int i=0;i
{
obRows[i]=(STRUCT)arow.get(i);
}

Please correct this....

pbsl said...

you have a nice site. thanks for sharing this valuable resources. keep it up. anyway, various kinds of ebooks are available here

http://feboook.blogspot.com

Anji said...

Hey Mukul,

Sorry to bother about this old topic.

I am getting exception in OAF AM
Datum[] arrMessage = message_display.getOracleArray();

Error is :
oracle.apps.fnd.framework.OAException: java.sql.SQLException: Internal Error

Here is my full code
---------------------

OADBTransactionImpl oadbtransactionimpl = (OADBTransactionImpl)getOADBTransaction();
OracleConnection conn = (OracleConnection)oadbtransactionimpl.getJdbcConnection();
OracleCallableStatement stmt=null;

ARRAY message_display = null;

try {
stmt=(OracleCallableStatement)conn.prepareCall("{CALL xx_get_retro_rates(?,?,?,?,?)}");
stmt.setString(1, SiteCode);
stmt.setString(2, PayCode);
stmt.setString(3, ProjectNum);
stmt.setDATE(4,WorkDate);
stmt.registerOutParameter(5, OracleTypes.ARRAY,"GFSFA_RTR_TAB");
stmt.execute();
//getting Array back
message_display = stmt.getARRAY(5);

//String [] errorMessages = null;
//errorMessages = (String []) message_display.getArray();

//Getting sql data types in oracle.sql.Datum array which will typecast the object types
Datum[] arrMessage = message_display.getOracleArray();
}
catch (Exception e)
{
throw OAException.wrapperException(e);
}

Thanks in Advance.

Regards,
Anji

Anji said...

Hey Mukul,

I found the bug. The problem with synonyms to TYPE Variable. I created TYPE in Apps and issue resolved.

Thanks,
Anji

Unknown said...

Hi Mukul

I have some doubt in PPR. When we use PPR then we perform ACTION in process form Request but when response will come from application that time process Request call or process Form Request call. I am confusion for this topic