The following is not only a personal reminder for me but also a first try to answer some questions on Stack Overflow…
You can create custom object types in an Oracle database like so:
CREATE TYPE t_demo_object AS OBJECT ( some_number NUMBER, some_string varchar2(32) ) / |
Those types can have member functions, custom constructors and some pretty neat features inside oracle.
There also exists tables of them like so
CREATE TYPE t_demo_objects IS TABLE OF t_demo_object / |
They can be used to do stuff like pipelined functions.
Often those types are used as results of stored functions and procedures, probably like so:
CREATE OR REPLACE PROCEDURE p_generate_demo_objects(p_num IN NUMBER, p_data OUT t_demo_objects) AS BEGIN p_data := t_demo_objects(); p_data.extend(p_num); FOR i IN 1..p_num LOOP p_data(i) := t_demo_object(i, 'Index ' || i); END LOOP; END p_generate_demo_objects; / |
There was a comment by sande, asking for an example on how to pass ARRAYs to stored procedures, so here is a stored procedure receiving data as well as a table to store it:
CREATE TABLE demo ( some_number NUMBER, some_string varchar2(32) ); / CREATE OR REPLACE PROCEDURE p_receive_demo_objects(p_data IN t_demo_objects) AS BEGIN FOR idx IN p_data.first .. p_data.last LOOP INSERT INTO demo(some_number, some_string) VALUES(p_data(idx).some_number, p_data(idx).some_string); END LOOP; COMMIT; END p_receive_demo_objects; / |
As long as this is used only within the database, everything is fine with Oracle Object Types… But often a procedure like this will be called from the outside through jdbc.
How to retrieve this object type? As an java.sql.Array of java.sql.Structs.
I’m doing it like so with minimal dependencies on the Oracle JDBC driver:
package tests.jdbc; import java.sql.Array; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSetMetaData; import java.sql.Struct; import java.sql.Types; import oracle.sql.ARRAY; import oracle.sql.ArrayDescriptor; import oracle.sql.STRUCT; import oracle.sql.StructDescriptor; public class OracleTableOfResult { public static void main(String...a) throws Exception { Class.forName("oracle.jdbc.OracleDriver"); Connection connection = DriverManager.getConnection("jdbc:oracle:thin:<USER>/<PASS>@<DATABASEHOST>:1521:<SERVICE>"); final String typeName = "T_DEMO_OBJECT"; final String typeTableName = "T_DEMO_OBJECTS"; // Get a description of your type (Oracle specific) final StructDescriptor structDescriptor = StructDescriptor.createDescriptor(typeName.toUpperCase(), connection); final ResultSetMetaData metaData = structDescriptor.getMetaData(); // Call the procedure (or whatever else) that returns the table of a custom type CallableStatement cs = connection.prepareCall("{call p_generate_demo_objects(?, ?)}"); cs.setInt(1, 5); // Result is an java.sql.Array... cs.registerOutParameter(2, Types.ARRAY, typeTableName); cs.execute(); // ...who's elements are java.sql.Structs Object[] data = (Object[]) ((Array) cs.getObject(2)).getArray(); for(Object tmp : data) { Struct row = (Struct) tmp; // Attributes are index 1 based... int idx = 1; for(Object attribute : row.getAttributes()) { System.out.println(metaData.getColumnName(idx) + " = " + attribute); ++idx; } System.out.println("---"); } cs.close(); // See sandes comment, passing arrays of Objects // We need the array descriptor as well final ArrayDescriptor arrayDescriptor = ArrayDescriptor.createDescriptor(typeTableName.toUpperCase(), connection); // Create generic object array // Create ARRAY from array of STRUCTS final ARRAY demoObjectsFromJava = new ARRAY( arrayDescriptor, connection, new STRUCT[] { // STRUCTS are created with the struct descriptor and a generic object array containing the values of the // attributes of the T_DEMO_OBJECT new STRUCT(structDescriptor, connection, new Object[] {23, "Testobject 1"}), new STRUCT(structDescriptor, connection, new Object[] {42, "Testobject 2"}) } ); cs = connection.prepareCall("{call p_receive_demo_objects(?)}"); // setObject with the designated sql type cs.setObject(1, demoObjectsFromJava, Types.ARRAY); cs.execute(); // no output here, have a look in table demo cs.close(); connection.close(); } } |
In the original question is also room for improving the pl/sql procedure.
You can fill the resulting object procedural with a loop but you can also use a “bulk collect into”:
SELECT t_demo_object(colum_a, column_b) bulk collect INTO p_data FROM some_table; |
which should be considerable faster on large datasets.
Note that it is not possible to use package level array types of record types in JDBC:
Oracle JDBC does not support RAW, DATE, and PL/SQL RECORD as element types.
Accessing PL/SQL Index-by Tables
Sadly, we have a legacy application that makes extensive use of such types… We had to rewrite most of the procedures.
34 comments
Thank YOU!!!!!!
I got the STRUCT, put then got stuck ….
Hi Dave, thank you for your feedback, you’re welcome.
Thanks for sharing !!
This blog was very helpful 🙂
There is a probleme with oracle 11G, JAVA 1.6
I get this result:
SOME_NUMBER = 1
SOME_STRING = ???
—
SOME_NUMBER = 2
SOME_STRING = ???
—
SOME_NUMBER = 3
SOME_STRING = ???
—
SOME_NUMBER = 4
SOME_STRING = ???
—
SOME_NUMBER = 5
SOME_STRING = ???
Any idea plz?
Hi,
what’s the problem? You getting weird encodings?
Thanks for the information.
Can you please share an example to pass Java object which represents t_demo_objects as an input parameter to stored procedure
Sure, sande. You’re welcome. Passing table of objects is also not hard, see updated post, especially the new receive function and that part of the Java class:
Hi,
This part of code
[CODE]// …who’s elements are java.sql.Structs
Object[] data = (Object[]) ((Array) cs.getObject(2)).getArray();
for(Object tmp : data) {
Struct row = (Struct) tmp;
// Attributes are index 1 based…
int idx = 1;
for(Object attribute : row.getAttributes()) {
System.out.println(metaData.getColumnName(idx) + ” = ” + attribute);
++idx;
}
System.out.println(“—“);
}[/CODE]
return
[CODE]
SOME_NUMBER = 1
SOME_STRING = ???
—
[/CODE]
Have you any idea
İs there any comment for hicham’s character/encoding problem. I am also getting same result
I have found this code doesn’t work if you use a JNDI connection (as opposed to using JDBC) 🙂
Intersting… First guess would be that you’re getting a wrapped connection, but i’m not using specifics of an Oracle Connection. Whats the exact problem?
Sure – so I can create two kinds of database connections. One using the device driver the other by going into Weblogic console and creating a JNDI and referencing it in the code. If I use the former it works 100% but it’s not practical. Weblogic gives us the options of connection pooling. If I generate the JNDI and then invoke it like this:
public static Connection getConn() {
Connection db = null;
//OracleConnection orConn = null;
try {
Context c = new InitialContext();
DataSource ds = (DataSource) c.lookup(“jdbc/pme”);
db = ds.getConnection();
//orConn = (OracleConnection) db;
}
catch (SQLException e) {
e.printStackTrace();
logger.fatal(e.getMessage() + e.getCause() );
}
catch (NamingException n) {
n.printStackTrace();
logger.fatal(n.getMessage() + n.getCause() );
}
// return orConn;
return db;
}
Then I use this database connection returned from the code into this:
final StructDescriptor structDescriptor = StructDescriptor.createDescriptor(objectType.toUpperCase(), dbConnection);
Then it will fail …
Hi, I know this is an old blog post but it consistently comes up on searches. I’m currently having an issue I have a stored procedure:
procedure update_person(p_person in person)
This seems fine and can use a STRUCT to populate person until you see that person is made up of :
CREATE OR REPLACE TYPE person as object(
P_FORENAME VARCHAR2(20)
,P_SURNAME VARCHAR2(20)
,P_DATE_OF_BIRTH DATE
,P_CONTACT_DETAILS db_contact_details_table
)
where db_contact_details_table is defined as:
CREATE OR REPLACE TYPE db_contact_details_table as VARRAY(100) OF db_contact_details,
CREATE OR REPLACE TYPE db_contact_details as object(
PHONE_NUMBER VARCHAR2(12)
,EMAIL_ADDRESS VARCHAR2(100)
)
How is this nesting of custom types handled and how would you model this in Java?
Hi Chris, no problem.
I’ve done this before, let me check this days… I’ll drop you an email.
Hi Michael,
Just to let you know I got past that problem in my previous post (using JNDI). Your hunch was correct. I used a wrapped connection and it worked fine!
Cheers
Bob
Hi Michael,
Brilliant thanks very much
Chris
Hi Chris,
here’s a version that works with nested objects. I’ve added your “db_contact_details” to my t_demo, so that i didn’t have to rewrite all:
This is the PL/SQL part:
Now for the changes on the Java Side: Nested object arrays behave exactly the same as on top level, you basically only need the additional ARRAY and STRUCT descriptors:
There’s little change in the basics.
Modeling this in Java: Not directly on the ARRAYs, i guess 🙂 But if you want to access them by hand and not using a framework, the above is the way to go, i think.
Bob, Thanks for your feedback! I appreciate it and I’m happy that your solved your problem!
Hi Michael,
Thanks for the help, this should be of great help to me 🙂
Chris
Excellent post Michael.
I have not used oracle db much, let alone stored procedures with such advanced concepts. I was struggling to develop higher-level utility/driver on top of JDBC to connect to oracle stored procedures. This example of yours, made my day.
Thank you very much.
bhandi
Thanks a lot for your friendly feedback!
Anybody who has ??? instead original string or null value for strings – you need to add orai18n.jar in your classpath.
Can the adding part be made dynamic.. Like i have a list instead of hard coded 23 and 42 .. i need to loop and add these values to the array ..
new STRUCT(structDescriptor, connection, new Object[]{42, “Testobject 2”})
Any help is appreciated 🙂
Hi bedware,
Even though i have orai18n.jar in my classpath i’m facing with the same problem . How did you solve it?
Hi
if i added one more object
CREATE TYPE t_demo_object_two AS OBJECT (
some_number_two number,
some_string_two varchar2(32),
t_demo_objects t_demo_objects
)
/
CREATE TYPE t_demo_objects_two IS TABLE OF t_demo_object_two
/
can you please provide the example for this
Hi
if i added one more object
CREATE TYPE t_demo_object_two AS OBJECT (
some_number_two number,
some_string_two varchar2(32),
t_demo_objects t_demo_objects
)
/
CREATE TYPE t_demo_objects_two IS TABLE OF t_demo_object_two
/
can you please provide the example for this
Hi,
Do you whether this works getting the connection from a DataSource configured inside WebLogic Server?
I’m facing the error below:
Exception in thread “main” weblogic.rmi.extensions.RemoteRuntimeException: Unexpected Exception
at weblogic.jdbc.rmi.internal.ArrayImpl_oracle_sql_ARRAY_1036_WLStub.getArray(Unknown Source)
at weblogic.jdbc.rmi.internal.ArrayStub_weblogic_jdbc_rmi_internal_ArrayImpl_oracle_sql_ARRAY_1036_WLStub.getArray(Unknown Source)
at OracleTableOfResult.main(OracleTableOfResult.java:56)
Caused by: java.rmi.MarshalException: error marshalling return; nested exception is:
java.io.NotSerializableException: oracle.jdbc.driver.T4CConnection
at weblogic.rjvm.ResponseImpl.unmarshalReturn(ResponseImpl.java:237)
at weblogic.rmi.internal.BasicRemoteRef.invoke(BasicRemoteRef.java:223)
… 3 more
Caused by: java.io.NotSerializableException: oracle.jdbc.driver.T4CConnection
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1518)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1483)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)
at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1346)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1154)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)
at weblogic.rjvm.MsgAbbrevOutputStream.writeObject(MsgAbbrevOutputStream.java:618)
at weblogic.utils.io.ChunkedObjectOutputStream.writeObject(ChunkedObjectOutputStream.java:62)
at weblogic.jdbc.rmi.internal.ArrayImpl_oracle_sql_ARRAY_WLSkel.invoke(Unknown Source)
at weblogic.rmi.internal.BasicServerRef.invoke(BasicServerRef.java:667)
at weblogic.rmi.internal.BasicServerRef$1.run(BasicServerRef.java:522)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:363)
at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:146)
at weblogic.rmi.internal.BasicServerRef.handleRequest(BasicServerRef.java:518)
at weblogic.rmi.internal.wls.WLSExecuteRequest.run(WLSExecuteRequest.java:118)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)
Sorry, I don’t know.
Hi Michael,
I am using the approach suggested by you. Below is my code for SQL
————
CREATE OR REPLACE PACKAGE TXN_CONSOLIDATION_PKG AS
TYPE TRAN_DETAIL IS RECORD
(
col1 VARCHAR2(40),
col2 VARCHAR2(40),
col3 VARCHAR2(40),
);
TYPE tran_details IS TABLE OF TRAN_DETAIL;
PROCEDURE TXN_CONSOLIDATION_PROC (tableName IN VARCHAR2, tableType IN VARCHAR2, startDate IN VARCHAR2, endDate IN VARCHAR2,
cardNumber IN VARCHAR2, rowCount IN INTEGER, detail OUT tran_details);
END TXN_CONSOLIDATION_PKG;
———————————————-
Java Code
———————————————-
Connection c = DriverManager.getConnection(“url”, “username”,”pwd”);
String proc = “{call TXN_CONSOLIDATION_PKG.TXN_CONSOLIDATION_PROC(?,?,?,?,?,?,?)}”;
final String typeName = “TXN_CONSOLIDATION_PKG.TRAN_DETAIL”;
final String typeTableName = “TXN_CONSOLIDATION_PKG.tran_details”;
final StructDescriptor structDescriptor = StructDescriptor.createDescriptor(typeName.toUpperCase(), c);
final ResultSetMetaData metaData = structDescriptor.getMetaData();
OracleCallableStatement s = (OracleCallableStatement)c.prepareCall(proc);
s.setString(1, “a”);
s.setString(2, “b”);
s.setString(3, “01/01/2019 00:00:00”);
s.setString(4, “11/01/2019 00:00:00”);
s.setString(5, “5000888823566634”);
s.setInt(6, 100);
s.registerOutParameter(7, Types.ARRAY, typeTableName);
s.execute();
—————————————————
Error received at line s.registerOutParameter(7, Types.ARRAY, typeTableName);
Exception in thread “main” java.sql.SQLException: ORA-06550: line 1, column 83:
PLS-00103: Encountered the symbol “;” when expecting one of the following:
( begin case declare end exception exit for goto if loop mod
null pragma raise return select update while with
<< continue close current delete fetch lock
insert open rollback savepoint set sql execute commit forall
merge pipe purge
The symbol "exit" was substituted for ";" to continue.
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:879)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
at oracle.jdbc.driver.T4CCallableStatement.doOall8(T4CCallableStatement.java:204)
at oracle.jdbc.driver.T4CCallableStatement.executeForRows(T4CCallableStatement.java:1041)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1329)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3584)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3665)
at oracle.jdbc.driver.OracleCallableStatement.executeUpdate(OracleCallableStatement.java:4739)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1352)
Hi Michael,
This code absolutely saved my bacon! Oracle JDBC with tables of objects appears to be a bit of a lost art.
I much appreciate you sharing your knowledge.
Eric
Thanks for your kind feedback, Eric!
Hi Michael…
Your code has saved my day. Found Very helpful after googling for a week on JDBC, collections and arrays in java n oracle.
How to avoid hardcoding of values for the object columns ?
new STRUCT(structDescriptor, connection, new Object[] {23, “Testobject 1”}),
new STRUCT(structDescriptor, connection, new Object[] {42, “Testobject 2”})
//nested
new STRUCT(structDescriptor, connection, new Object[]{23, “Testobject 1”, detailsTable}),
new STRUCT(structDescriptor, connection, new Object[]{42, “Testobject 2”, detailsTable})
Your help is much appreciated!!
— Sreeni
Dear Sir/Madam,
I am really happy for sharing knowledge and it was very helpful to me.
Requirement:
I am calling stored procedure using mybatis(ibatis) in java spring boot project.
Input: String
And procedure is returing 2 out param values. 1 o_batch_size(i am getting in the response). 2. o_Message_data_array(i am getting as empty object).
I am getting empty object since which i was written Typehandler is not correct. I am guessing.
Can you please help me with this.
Procedure snippet:
Procedure Poll_File_Transmission
(in_trans_type IN btps.transmission_header.th_trans_type%type,
o_message_data_array OUT btps.Obj_File_Trans_Poll_Array,
o_batch_size OUT NUMBER) IS
v_state_agency_obj btps.obj_payer_state_data_array;
CURSOR pension_ptf_cur IS
SELECT th_admin_id admin_id,
—-
FROM btps.transmission_header,
—
btps.participant
WHERE td_transmission_id = th_transmission_id
—-
AND eptr_return_address_id = adr2.addr_address_id(+);
CURSOR pension_pmt_cur IS
SELECT th_admin_id admin_id,
tdf_service_id service_id,
—
FROM btps.transmission_header,
—
benefits.product_service_ref
WHERE td_transmission_id = th_transmission_id
—
AND cpy_admin_id = ad_admin_id;
END IF; — in_trans_type = BTPS.BTPS_CONSTANTS_PACKAGE.C_RPS_TYPE
— All done.
—
END Poll_File_Transmission;
Sir/Madam: If you don’t mind I would be pay if any charge applicable. I am in bit in difficult situatuion. Please do the needful.
I am happy If you can able to spend 5 of minutes your valuable time.
Mail Id: brahmasuresh@gmail.com
Ph No: 9739744111
I am waiting for your reply. Thank you.
Regards
BrahmaSuresh. S
Sorry Vijaya, i have never used MyBatis, but I’m gonna share this on my twitter, maybe someone has an idea.
Post a Comment