sql >> Base de Datos >  >> RDS >> Oracle

Lea un ARRAY de un STRUCT devuelto por un procedimiento almacenado

Crear objetos que implementen java.sql.SQLData . En este escenario, cree TEnclosure y TAnimal clases, que implementan SQLData .

Solo para su información, en las versiones más recientes de Oracle JDBC, tipos como oracle .sql.ARRAY están en desuso a favor de java.sql tipos Aunque no estoy seguro de cómo escribir una matriz (que se describe a continuación) usando solo java.sql API.

Cuando implementas readSQL() lee los campos en orden. Obtienes un java.sql.Array con sqlInput.readArray() . Entonces TEnclosure.readSQL() se vería algo como esto.

@Override
public void readSQL(SQLInput sqlInput, String s) throws SQLException {
    id = sqlInput.readBigDecimal();
    name = sqlInput.readString();
    Array animals = sqlInput.readArray();
    // what to do here...
}

Nota:readInt() también existe, pero Oracle JDBC parece proporcionar siempre BigDecimal para NUMBER

Notará que algunas API como java.sql.Array tienen métodos que toman un tipo map Map<String, Class<?>> Esta es una asignación de nombres de tipos de Oracle a su clase Java correspondiente implementando SQLData (ORAData puede funcionar también?).

Si simplemente llama a Array.getArray() , obtendrá Struct objetos a menos que el controlador JDBC conozca sus asignaciones de tipo a través de Connection.setTypeMap(typeMap) . Sin embargo, configurar typeMap en la conexión no funcionó para mí, así que uso getArray(typeMap)

Crea tu Map<String, Class<?>> typeMap en algún lugar y agregue entradas para sus tipos:

typeMap.put("T_ENCLOSURE", TEnclosure.class);
typeMap.put("T_ANIMAL", TAnimal.class);

Dentro de un SQLData.readSQL() implementación, llame a sqlInput.readArray().getArray(typeMap) , que devuelve Object[] donde el Object entradas o de tipo TAnimal .

Por supuesto, el código para convertir a una List<TAnimal> se vuelve tedioso, así que solo use esta función de utilidad y ajústela a sus necesidades en cuanto a la política de lista nula versus lista vacía:

/**
 * Constructs a list from the given SQL Array
 * Note: this needs to be static because it's called from SQLData classes.
 *
 * @param <T> SQLData implementing class
 * @param array Array containing objects of type T
 * @param typeClass Class reference used to cast T type
 * @return List<T> (empty if array=null)
 * @throws SQLException
 */
public static <T> List<T> listFromArray(Array array, Class<T> 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<T> list = new ArrayList<>(objectArray.length);
    for (Object o : objectArray) {
        list.add(typeClass.cast(o));
    }
    return list;
}

Escribir matrices

Descubrir cómo escribir una matriz fue frustrante, las API de Oracle requieren una conexión para crear una matriz, pero no tiene una conexión obvia en el contexto de writeSQL(SQLOutput sqlOutput) . Afortunadamente, este blog tiene un truco para obtener OracleConnection , que he usado aquí.

Cuando crea una matriz con createOracleArray() usted especifique el tipo de lista (T_ARRAY_ANIMALS ) para el nombre del tipo, NO el tipo de objeto singular.

Aquí hay una función genérica para escribir matrices. En tu caso, listType sería "T_ARRAY_ANIMALS" y pasarías en List<TAnimal>

/**
 * 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 <T> 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 <T> void writeArrayFromList(SQLOutput sqlOutput, String listType, @Nullable List<T> 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);
}

Notas:

  • En un momento pensé setTypeMap era obligatorio, pero ahora, cuando elimino esa línea, mi código sigue funcionando, así que no estoy seguro de si es necesario.
  • No estoy seguro de si debe escribir nulo o una matriz vacía, pero asumí que la matriz vacía es más correcta.

Consejos sobre los tipos de Oracle

  • Oracle escribe todo en mayúsculas, por lo que todos los nombres de tipo deben estar en mayúsculas.
  • Es posible que deba especificar SCHEMA.TYPE_NAME si el tipo no está en su esquema predeterminado.
  • Recuerde grant execute en tipos si el usuario con el que se está conectando no es el propietario.
    Si ha ejecutado en el paquete, pero no en el tipo, getArray() lanzará una excepción cuando intente buscar metadatos de tipo.

Primavera

Para desarrolladores que usan Spring , puede consultar Spring Data JDBC Extensions , que proporciona SqlArrayValue y SqlReturnArray , que son útiles para crear un SimpleJdbcCall para un procedimiento que toma una matriz como argumento o devuelve una matriz.

Capítulo 7.2.1 Establecer valores ARRAY usando SqlArrayValue para un parámetro IN explica cómo llamar a procedimientos con parámetros de matriz.