Stephen Freeman Rotating Header Image

February, 2010:

Calling an Oracle stored procedure with a Table parameter with Spring's StoredProcedure class

I don’t normally do this sort of thing, but this took my colleague Tony Lawrence and me a while to figure out and we didn’t find a good explanation on the web. This will be a very dull posting unless you need to fix this particular problem. Sorry about that.

We happen to be using the Spring database helper classes to talk to Oracle with stored procedures. It turns out that there’s a bug in the driver that means that you have to jump through a few hoops to pass values in when the input parameter type is a table. This should be equivalent to an array, but apparently it isn’t, so you have to set up the callable statement correctly. Where to do this was not obvious (to us) in the Spring framework.

Here’s an example stored procedure declaration:

CREATE TYPE VARCHARTAB IS TABLE OF VARCHAR2(255);

CREATE PACKAGE db_package {
  TYPE list_type IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
PROCEDURE a_stored_procedure(
  table_in IN list_type
)

The table_in parameter type list_type is declared within a package, which means we can’t declare the parameter as an OracleTypes.ARRAY when setting up the statement. Instead we declare it as the type of the table contents OracleTypes.VARCHAR

class MyProcedure extends StoredProcedure {
  public MyProcedure(DataSource dataSource) {
    super(dataSource, "db_package.a_stored_procedure");
    declareParameter(new SqlParameter("table_in",
                                      OracleTypes.VARCHAR));
    compile();
  }

  void call(String... values) {
    execute(withParameters(values));
  }

Here’s the money quote. When setting up the parameter, you need to provide it with a SqlTypeValue.
Don’t use one of the helper base classes that come out of the box, but create an implementation directly. That gives you access to the statement, which you can cast and set appropriately.

   private Map withParameters(String... values) {
      return ImmutableMap.of("table_in",
                             oracleIndexTableWith(50, values));
   }

   private   SqlTypeValue
   oracleIndexTableWith(final int elemMaxLen, final T... values) {
     return new SqlTypeValue() {
       @Override
       public void setTypeValue(
         PreparedStatement statement, int paramIndex,
         int sqlType, String typeName) throws SQLException
      {
         ((OracleCallableStatement)statement).setPlsqlIndexTable(
            paramIndex, values, values.length, values.length,
            sqlType, elemMaxLen);
       }
     };
   }
}

That’s it. Happy copy and paste.