Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Ler um ARRAY de um STRUCT retornado por um procedimento armazenado


Crie objetos que implementam java.sql.SQLData . Nesse cenário, crie TEnclosure e TAnimal classes, que implementam SQLData .

Apenas para sua informação, nas versões mais recentes do Oracle JDBC, tipos como oracle .sql.ARRAY estão obsoletos em favor de java.sql tipos. Embora eu não tenha certeza de como escrever um array (descrito abaixo) usando apenas java.sql API.

Quando você implementa readSQL() você lê os campos em ordem. Você obtém um java.sql.Array com sqlInput.readArray() . Então TEnclosure.readSQL() ficaria algo assim.
@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() também existe, mas o Oracle JDBC parece sempre fornecer BigDecimal para NUMBER

Você notará que algumas APIs como java.sql.Array tem métodos que recebem um mapa de tipo Map<String, Class<?>> Este é um mapeamento de nomes de tipo Oracle para sua classe Java correspondente implementando SQLData (ORAData pode funcionar também?).

Se você apenas chamar Array.getArray() , você obterá Struct objetos, a menos que o driver JDBC saiba sobre seus mapeamentos de tipo por meio de Connection.setTypeMap(typeMap) . No entanto, definir typeMap na conexão não funcionou para mim, então eu uso getArray(typeMap)

Crie seu Map<String, Class<?>> typeMap em algum lugar e adicione entradas para seus tipos:
typeMap.put("T_ENCLOSURE", TEnclosure.class);
typeMap.put("T_ANIMAL", TAnimal.class);

Dentro de um SQLData.readSQL() implementação, chame sqlInput.readArray().getArray(typeMap) , que retorna Object[] onde o Object entradas ou do tipo TAnimal .

Claro que o código para converter para um List<TAnimal> fica entediante, então apenas use esta função de utilitário e ajuste-a para suas necessidades na medida em que a política de lista nula vs vazia:
/**
 * 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;
}

Gravando matrizes

Descobrir como escrever uma matriz foi frustrante, as APIs Oracle exigem uma conexão para criar uma matriz, mas você não tem uma conexão óbvia no contexto de writeSQL(SQLOutput sqlOutput) . Felizmente, este blogue tem um truque/hack para obter o OracleConnection , que usei aqui.

Quando você cria um array com createOracleArray() você especifica o tipo de lista (T_ARRAY_ANIMALS ) para o nome do tipo, NÃO o tipo de objeto singular.

Aqui está uma função genérica para escrever arrays. No seu caso, listType seria "T_ARRAY_ANIMALS" e você passaria 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:
  • A certa altura, pensei em setTypeMap era necessário, mas agora, quando removo essa linha, meu código ainda funciona, então não tenho certeza se é necessário.
  • Não tenho certeza se você deve escrever null ou uma matriz vazia, mas presumi que a matriz vazia é mais correta.

Dicas sobre os tipos Oracle
  • O Oracle coloca tudo em maiúsculas, portanto, todos os nomes de tipo devem ser em maiúsculas.
  • Talvez seja necessário especificar SCHEMA.TYPE_NAME se o tipo não estiver em seu esquema padrão.
  • Lembre-se de grant execute em tipos se o usuário com o qual você está se conectando não for o proprietário.
    Se você tiver executado no pacote, mas não no tipo, getArray() lançará uma exceção quando tentar procurar metadados de tipo.

Primavera

Para desenvolvedores que usam o Spring , você pode dar uma olhada em Spring Data JDBC Extensions , que fornece SqlArrayValue e SqlReturnArray , que são úteis para criar um SimpleJdbcCall para um procedimento que recebe um array como argumento ou retorna um array.

Capítulo 7.2.1 Definindo valores ARRAY usando SqlArrayValue para um parâmetro IN explica como chamar procedimentos com parâmetros de matriz.