How to call oracle stored procedure which include user-defined type in java?
Asked Answered
E

4

30

In Oracle DB:

I have the following stored procedure:

procedure getInfo ( p_ids IN IDS_TABLE, p_details OUT cursor )

Type IDS_TABLE is:

create or replace type IDS_TABLE as table of IDS    

create or replace type IDS as object ( id1 NUMBER, id2 NUMBER, id3 NUMBER )

How can I call getInfo in Java?

Emboss answered 2/9, 2010 at 10:8 Comment(1)
Similar to Read an ARRAY from a STRUCT returned by a stored procedureLeatherleaf
M
40

Setting up a link between Oracle SQL objects and java objects manually is not a trivial task. In particular, arrays (or nested tables) of user-defined objects are more complex to pass from java to Oracle than arrays of standard datatypes. In other words, it is easier to call a procedure with signature:

(TABLE OF NUMBER, TABLE OF NUMBER, TABLE OF NUMBER)`

than a procedure whose signature is:

(TABLE OF (NUMBER, NUMBER, NUMBER))   <- your case

You can write a wrapper around your procedure to transform the second case into the first case.


That being said, it is by far not impossible to map your procedure. The following example is largely inspired by a post by Tom Kyte. Tom describes how to map a TABLE OF NUMBER using oracle.sql.ARRAY. In your case we will also have to use oracle.sql.STRUCT to map the IDS SQL object.

You may also want to browse the Oracle JDBC doc, in particular the chapter Working with Oracle Object Types.

First is a setup similar to yours:

SQL> CREATE OR REPLACE TYPE IDS AS OBJECT ( id1 NUMBER, id2 NUMBER, id3 NUMBER );
  2  /
Type created

SQL> CREATE OR REPLACE TYPE IDS_TABLE AS TABLE OF IDS;
  2  /
Type created

SQL> CREATE OR REPLACE PROCEDURE getInfo(p_ids IN IDS_TABLE) IS
  2  BEGIN
  3     FOR i IN 1 .. p_ids.COUNT LOOP
  4        dbms_output.put_line(p_ids(i).id1
  5                             || ',' || p_ids(i).id2
  6                             || ',' || p_ids(i).id3);
  7     END LOOP;
  8  END getInfo;
  9  /     
Procedure created

This is the java procedure:

SQL> CREATE OR REPLACE
  2  AND COMPILE JAVA SOURCE NAMED "ArrayDemo"
  3  as
  4  import java.io.*;
  5  import java.sql.*;
  6  import oracle.sql.*;
  7  import oracle.jdbc.driver.*;
  8  
  9  public class ArrayDemo {
 10  
 11     public static void passArray() throws SQLException {
 12  
 13        Connection conn =
 14           new OracleDriver().defaultConnection();
 15  
 16  
 17        StructDescriptor itemDescriptor =
 18           StructDescriptor.createDescriptor("IDS",conn);
 19  
 20        Object[] itemAtributes = new Object[] {new Integer(1),
 21                                               new Integer(2),
 22                                               new Integer(3)};
 23        STRUCT itemObject1 = new STRUCT(itemDescriptor,conn,itemAtributes);
 24  
 25        itemAtributes = new Object[] {new Integer(4),
 26                                      new Integer(5),
 27                                      new Integer(6)};
 28        STRUCT itemObject2 = new STRUCT(itemDescriptor,conn,itemAtributes);
 29  
 30        STRUCT[] idsArray = {itemObject1,itemObject2};
 31  
 32        ArrayDescriptor descriptor =
 33           ArrayDescriptor.createDescriptor( "IDS_TABLE", conn );
 34  
 35        ARRAY array_to_pass =
 36           new ARRAY( descriptor, conn, idsArray );
 37  
 38        OraclePreparedStatement ps =
 39           (OraclePreparedStatement)conn.prepareStatement
 40           ( "begin getInfo(:x); end;" );
 41  
 42        ps.setARRAY( 1, array_to_pass );
 43        ps.execute();
 44  
 45     }
 46  }
 47  /
Java created

Let's call it:

SQL> CREATE OR REPLACE
  2  PROCEDURE show_java_calling_plsql
  3  AS LANGUAGE JAVA
  4  NAME 'ArrayDemo.passArray()';
  5  /
Procedure created

SQL> exec show_java_calling_plsql ;
1,2,3
4,5,6

PL/SQL procedure successfully completed
Mariehamn answered 2/9, 2010 at 13:36 Comment(5)
If I was to change the table declaration for an associative array i.e. create or replace type IDS_TABLE as table of IDS index by varchar2(50) , what changes do I need to make to the java code?, I opened a question for this. #12732242Dipterocarpaceous
I am runnig the java code from eclipse. But the inputs are going null. My case is Table of objects. I opened a queston for this. #20100405Eolithic
@Vincet Malgrat What if create or replace type IDS as object ( id1 NUMBER, id2 NUMBER, id3 NUMBER ) again conatins an array like create or replace type IDS as object ( IDS_query IDS_QUERY_TYPE ) and again IDS_QUERY_TYPE is defined as CREATE OR REPLACE TYPE IDS_QUERY_TYPE AS TABLE OF varchar2(100) then how to define the struct? Help PleaseNefertiti
@NikhilAgrawal You would use an Array to define IDS_QUERY_TYPE, then a Struct (with the preceding array as elements) to define IDS, then an Array (with the preceding Struct as elements) to define IDS_TABLE. See this example on pastebin. Note that the STRUCT constructor is deprecated in the newer versions of java, use Connection.createStruct instead if you have java 1.6 or above.Mariehamn
@VincentMalgrat Thanks very much. It worked perfectly.Nefertiti
L
1

If you're using Spring, you may want to look at Spring Data JDBC Extensions, which provides a SqlArrayValue type.

Chapter 7.2.1 Setting ARRAY values using SqlArrayValue for an IN parameter explains how to call procedures with array parameters.

Leatherleaf answered 24/8, 2017 at 17:13 Comment(1)
This link is not working anymoreGasiform
P
0

This is a pretty good example. if you see java.sql.SQLException: invalid name pattern: still. Check the scope of the type that you declared in Oracle. I am using Oracle 11g and had to declare both Object of String Array and Table of Objects of my type in schema level. Spent some 3 hours and found that.

oracle.sql.StructDescriptor docObjDescriptor = StructDescriptor.createDescriptor("SSIADM.DOCUMENT_OBJECT",conn);
  String[] strArray = new String[] {"doc1","file1"};             
  oracle.sql.STRUCT DocObject1 = new STRUCT(docObjDescriptor,conn,strArray);

   strArray = new String[] {"doc2","file2"};
   oracle.sql.STRUCT DocObject2 = new STRUCT(docObjDescriptor,conn,strArray);

    oracle.sql.STRUCT[] docObjArray = {DocObject1,DocObject2};

    arrDesc = ArrayDescriptor.createDescriptor("DOCUMENT_TABLE", conn);
    oracle.sql.ARRAY array = new ARRAY(arrDesc, conn, docObjArray);
Pacifica answered 29/1, 2014 at 16:28 Comment(0)
F
0

The solution I used lets Spring parse the object instead of having to manually create the STRUCT arrays. Unfortunately, it still isn't environment independent.

Stored Proc DAO:

package ****.dao.storedProcedures;

import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlTypeValue;
import org.springframework.jdbc.object.StoredProcedure;

import ****.persistent.ComplexTypeObj;
import ****.persistent.InnerType;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;

public class SaveStoredProc extends StoredProcedure implements InitializingBean {

    public static final String IT_COMPLEX_TYPE = "it_complex_type";

    public SaveStoredProc() {
    }

    @Override
    public void afterPropertiesSet() {
        Validate.notNull(getJdbcTemplate());
        super.setFunction(true);
        super.declareParameter(new SqlOutParameter(RESULT, Types.NUMERIC));
        super.declareParameter(new SqlParameter(IT_COMPLEX_TYPE, Types.OTHER, ComplexTypeObj.ORACLE_OBJECT_NAME));
        compile();
    }

    public long execute(final ComplexTypeObj complexTypeObj) {
        Map<String, Object> inParameters = new HashMap<String, Object>();
        inParameters.put(IT_COMPLEX_TYPE, new ComplexSqlTypeValue(complexTypeObj));

        @SuppressWarnings("unchecked")
        Map<String, Object> resp = super.execute(inParameters);

        return ((Number)resp.get(RESULT)).longValue();
    }

    private static final class ComplexSqlTypeValue implements SqlTypeValue {
        private final Log logger = LogFactory.getLog(getClass());

        private final ComplexTypeObj complexTypeObj;

        public ComplexSqlTypeValue(ComplexTypeObj complexTypeObj) {
            this.complexTypeObj = complexTypeObj;
        }

        @Override
        public void setTypeValue(PreparedStatement ps, int paramIndex, int sqlType, String typeName) throws SQLException {
            Connection conn = ps.getConnection();
            try {
                conn = conn.unwrap(oracle.jdbc.OracleConnection.class);
            } catch (Exception e) {
                logger.debug("Could not unrap connection");
            }

            Map<String, Class<?>> typeMap = conn.getTypeMap();
            typeMap.put(typeName, ComplexTypeObj.class); //The name of the outer object type.
            typeMap.put(InnerType.ORACLE_OBJECT_NAME, InnerType.class); //The name of the inner object type.

            ArrayDescriptor des = ArrayDescriptor.createDescriptor(InnerType.ORACLE_LIST_NAME, conn); //The name of the inner list type.
            Array objArray = new ARRAY(des, conn, complexTypeObj.getInnerList().toArray());
            complexTypeObj.setInnerArray(objArray);

            ps.setObject(paramIndex, complexTypeObj);
        }
    }
}

Outer Type:

import java.sql.*;
import java.util.*;

public class OuterType extends BaseSQLData implements SQLData {

    public static final String ORACLE_OBJECT_NAME = "T_OUTER_TYPE";

    private List<InnerType> innerList;
    private Array innerArray;

    public OuterType() {
        this.innerList = new ArrayList<InnerType>();
    }

    public String getSQLTypeName() throws SQLException {
        return ORACLE_OBJECT_NAME;
    }

    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeArray(innerArray);
    }

Inner Type:

public final class InnerType extends BaseSQLData {
    public static final String ORACLE_OBJECT_NAME = "T_INNER_TYPE";
    public static final String ORACLE_LIST_NAME = "T_INNER_TYPE_LIST";

    private String valueA;
    private Long   valueB = 0;

    public String getSQLTypeName() throws SQLException {
        return ORACLE_OBJECT_NAME;
    }

    @Override
    public void readSQL(SQLInput stream, String typeName) throws SQLException {
        throw new UnsupportedOperationException("This class doesn't support read opperations.");
    }

    @Override
    public void writeSQL(SQLOutput stream) throws SQLException {
        stream.writeString(valueA);
        stream.writeBigDecimal(valueB == null ? null : new BigDecimal(valueB.toString()));
    }
Ferrocene answered 23/6, 2016 at 15:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.