0

I want to write a Java program that will insert a row to a table that has a number of nested tables. Following that, I want to insert an unpredictable number of rows to each of these nested tables.

There are lots of examples a PreparedStatement of this sort:

    new PreparedStatement("INSERT INTO CONTAINER_TBL (A, B, NESTED_TBL)
        VALUES ('X', 'Y', 
            NESTED_TBL_TYPE(NESTED_ROW_TYPE('Q', 99),
                            NESTED_ROW_TYPE('R', 999))
         )");

This is fine if I know ahead of time how many nested rows I'll need to insert. But what if I don't?

MT0
  • 143,790
  • 11
  • 59
  • 117

1 Answers1

3

Pass a Java array as a collection:

Oracle 12c Setup:

CREATE USER test_user IDENTIFIED BY password;
GRANT CREATE SESSION TO test_user;
ALTER USER test_user QUOTA UNLIMITED ON users;

CREATE TYPE test_user.nested_row_type AS OBJECT( a CHAR(1), b INTEGER );
/

CREATE TYPE test_user.nested_tbl_type AS TABLE OF test_user.nested_row_type;
/

CREATE TABLE test_user.container_tbl(
  a CHAR(1),
  b CHAR(1),
  nested_tbl test_user.nested_tbl_type
) NESTED TABLE nested_tbl STORE AS nested_tbl_tbl;

Java: (Using ojdbc7.jar)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;
import oracle.sql.ARRAY;

public class LoadOracleObjectCollection {
  public static void main(String[] args) {
    try{
      Class.forName("oracle.jdbc.OracleDriver");

      Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","test_user","password");

      Object[] objs = new Object[]{
        con.createStruct( "NESTED_ROW_TYPE", new Object[]{ "Q", 99 } ),
        con.createStruct( "NESTED_ROW_TYPE", new Object[]{ "R", 999 } )
      };

      ARRAY a = ((OracleConnection) con).createARRAY("NESTED_TBL_TYPE", objs);

      PreparedStatement st = con.prepareCall( "INSERT INTO container_tbl ( a, b, nested_tbl ) VALUES ( ?, ?, ? )" );

      st.setString( 1, "x" );
      st.setString( 2, "y" );
      ((OraclePreparedStatement) st).setARRAY( 3 , a );
      st.execute();
      st.close();
      con.close();
    } catch(ClassNotFoundException | SQLException e) {
      System.out.println(e);
    }
  }
}

Oracle Query

SELECT c.a, c.b, n.a, n.b
FROM   test_user.container_tbl c
       CROSS JOIN TABLE( c.nested_tbl ) n;

Results:

A B A          B
- - - ----------
x y Q         99
x y R        999

Version with older syntax:

Just passing to and from a dummy query (rather than inserting into the database) to also show how to retrieve an array of objects:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OraclePreparedStatement;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import oracle.sql.Datum;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;

public class ArrayOfObjectsTest
{
  public static void main( final String[] args ){
    try{
      Class.forName( "oracle.jdbc.OracleDriver" );

      Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","test_user","password");

      OracleConnection oc = (OracleConnection) con;
      StructDescriptor sd = new StructDescriptor( "NESTED_ROW_TYPE", oc );
      ArrayDescriptor  ad = new ArrayDescriptor( "NESTED_TBL_TYPE", oc );

      ARRAY array = new ARRAY( ad,oc,new STRUCT[]{
        new STRUCT(sd,oc,new Object[]{ 'P',99 } ),
        new STRUCT(sd,oc,new Object[]{ 'Q',999 } )
      } );

      OraclePreparedStatement st = (OraclePreparedStatement) con.prepareStatement( "SELECT ? FROM DUAL" );
      st.setARRAY( 1, array);
      ResultSet rs = st.executeQuery();

      while( rs.next() )
      {
        Object[] structs = (Object[]) rs.getArray( 1 ).getArray();
        for ( Object struct : structs )
        {
          Datum[] datums = ((STRUCT) struct).getOracleAttributes();
          System.out.println( datums[0].stringValue() + ", " + datums[1].intValue() ) );
        }
      }
      st.close();
      con.close();
    } catch (ClassNotFoundException | SQLException ex) {
      System.out.println( ex.getMessage() );
      ex.printStackTrace();
    }
  }  
}

Output:

P, 99
Q, 999

This compiled for me with ojdbc6.jar and worked with Oracle 11gR2. You should find the correct ojdbc version for your database and use that.

MT0
  • 143,790
  • 11
  • 59
  • 117
  • Thank you, MT0. Regrettably, our shop's Oracle JDBC software is old and doesn't support the createStruct() and createARRAY() (or createArrayOf()) methods, even though createStruct() and createArrayOf() compile cleanly. By the way, I put ojdbc7.jar first in my build path and still didn't get access to a createARRAY() method even casting the Connection to OracleConnection; did you mean createArrayOf()? – BCBSM Elly Jul 03 '18 at 16:52
  • @BCBSMElly I tried `createArrayOf()` and got it to compile but just got an error for an Unsupported Operation when executing. `createARRAY` showed as deprecated in `ojdc7.jar` but it would execute so I posted the version I had got to work... you may have better luck with `createArrayOf()`. – MT0 Jul 04 '18 at 09:35
  • @BCBSMElly Updated with a version using the older syntax. – MT0 Jul 04 '18 at 09:48