In the database are three Oracle custom types (simplified) as follows:
create or replace TYPE T_ENCLOSURE AS OBJECT(
ENCLOSURE_ID NUMBER(32,0),
ENCLOSUR
Create objects that implement java.sql.SQLData
. In this scenario, create TEnclosure
and TAnimal
classes, which both implement SQLData
.
Just FYI, in newer Oracle JDBC versions, types such as oracle.sql.ARRAY are deprecated in favor of java.sql
types. Although I'm not sure how to write an array (described bellow) using only java.sql
API.
When you implement readSQL()
you read fields in order. You obtain a java.sql.Array
with sqlInput.readArray()
. So TEnclosure.readSQL()
would look something like this.
@Override
public void readSQL(SQLInput sqlInput, String s) throws SQLException {
id = sqlInput.readBigDecimal();
name = sqlInput.readString();
Array animals = sqlInput.readArray();
// what to do here...
}
Note: readInt()
also exists, but Oracle JDBC seems to always provide BigDecimal
for NUMBER
You will notice that some APIs such as java.sql.Array
have methods that take a type map Map
This is a mapping of Oracle type names to their corresponding Java class implementing SQLData
(ORAData
may work too?).
If you just call Array.getArray()
, you will get Struct
objects unless the JDBC driver knows about your type mappings via Connection.setTypeMap(typeMap)
. However, setting typeMap on the connection didn't work for me, so I use getArray(typeMap)
Create your Map
somewhere and add entries for your types:
typeMap.put("T_ENCLOSURE", TEnclosure.class);
typeMap.put("T_ANIMAL", TAnimal.class);
Within a SQLData.readSQL()
implementation, call sqlInput.readArray().getArray(typeMap)
, which returns Object[]
where the Object
entries or of type TAnimal
.
Of course the code to convert to a List
gets tedious, so just use this utility function and adjust it for your needs as far as null vs empty list policy:
/**
* Constructs a list from the given SQL Array
* Note: this needs to be static because it's called from SQLData classes.
*
* @param SQLData implementing class
* @param array Array containing objects of type T
* @param typeClass Class reference used to cast T type
* @return List (empty if array=null)
* @throws SQLException
*/
public static List listFromArray(Array array, Class typeClass) throws SQLException {
if (array == null) {
return Collections.emptyList();
}
// Java does not allow casting Object[] to T[]
final Object[] objectArray = (Object[]) array.getArray(getTypeMap());
List list = new ArrayList<>(objectArray.length);
for (Object o : objectArray) {
list.add(typeClass.cast(o));
}
return list;
}
Writing Arrays
Figuring out how to write an array was frustrating, Oracle APIs require a Connection to create an Array, but you don't have an obvious Connection in the context of writeSQL(SQLOutput sqlOutput)
. Fortunately, this blog has a trick/hack to get the OracleConnection
, which I've used here.
When you create an array with createOracleArray()
you specify the list type (T_ARRAY_ANIMALS
) for the type name, NOT the singular object type.
Here's a generic function for writing arrays. In your case, listType
would be "T_ARRAY_ANIMALS"
and you would pass in List
/**
* Write the list out as an Array
*
* @param sqlOutput SQLOutput to write array to
* @param listType array type name (table of type)
* @param list List of objects to write as an array
* @param Class implementing SQLData that corresponds to the type listType is a list of.
* @throws SQLException
* @throws ClassCastException if SQLOutput is not an OracleSQLOutput
*/
public static void writeArrayFromList(SQLOutput sqlOutput, String listType, @Nullable List list) throws SQLException {
final OracleSQLOutput out = (OracleSQLOutput) sqlOutput;
OracleConnection conn = (OracleConnection) out.getSTRUCT().getJavaSqlConnection();
conn.setTypeMap(getTypeMap()); // not needed?
if (list == null) {
list = Collections.emptyList();
}
final Array array = conn.createOracleArray(listType, list.toArray());
out.writeArray(array);
}
Notes:
setTypeMap
was required, but now when I remove that line my code still works, so I'm not sure if it's necessary.Tips on Oracle types
SCHEMA.TYPE_NAME
if the type isn't in your default schema.grant execute
on types if the user you are connecting with is not the owner.getArray()
will throw an exception when it tries to look for type metadata.Spring
For developers using Spring, you may want to look at Spring Data JDBC Extensions, which provides SqlArrayValue
and SqlReturnArray
, which are useful for creating a SimpleJdbcCall
for a procedure that takes an array as an argument or returns an array.
Chapter 7.2.1 Setting ARRAY values using SqlArrayValue for an IN parameter explains how to call procedures with array parameters.