Oracle
 sql >> Database >  >> RDS >> Oracle

Legge un ARRAY da una STRUCT restituita da una stored procedure

Crea oggetti che implementano java.sql.SQLData . In questo scenario, crea TEnclosure e TAnimal classi, che implementano entrambe SQLData .

Per tua informazione, nelle versioni più recenti di Oracle JDBC, tipi come oracle .sql.ARRAY sono deprecati a favore di java.sql tipi. Anche se non sono sicuro di come scrivere un array (descritto di seguito) usando solo java.sql API.

Quando implementi readSQL() leggi i campi in ordine. Ottieni un java.sql.Array con sqlInput.readArray() . Quindi TEnclosure.readSQL() sembrerebbe qualcosa del genere.

@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() esiste anche, ma Oracle JDBC sembra fornire sempre BigDecimal per NUMBER

Noterai che alcune API come java.sql.Array hanno metodi che accettano una mappa di tipo Map<String, Class<?>> Questa è una mappatura dei nomi dei tipi Oracle alla classe Java corrispondente che implementa SQLData (ORAData potrebbe funzionare anche tu?).

Se chiami semplicemente Array.getArray() , otterrai Struct oggetti a meno che il driver JDBC non sia a conoscenza delle mappature dei tipi tramite Connection.setTypeMap(typeMap) . Tuttavia, l'impostazione di typeMap sulla connessione non ha funzionato per me, quindi utilizzo getArray(typeMap)

Crea la tua Map<String, Class<?>> typeMap da qualche parte e aggiungi voci per i tuoi tipi:

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

All'interno di un SQLData.readSQL() implementazione, chiama sqlInput.readArray().getArray(typeMap) , che restituisce Object[] dove l'Object voci o di tipo TAnimal .

Ovviamente il codice da convertire in un List<TAnimal> diventa noioso, quindi usa questa funzione di utilità e adattala alle tue esigenze per quanto riguarda la politica di elenco nullo o vuoto:

/**
 * 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;
}

Scrittura di array

Capire come scrivere un array è stato frustrante, le API Oracle richiedono una connessione per creare un array, ma non hai una connessione ovvia nel contesto di writeSQL(SQLOutput sqlOutput) . Fortunatamente, questo blog ha un trucco/hack per ottenere OracleConnection , che ho usato qui.

Quando crei un array con createOracleArray() specificare il tipo di elenco (T_ARRAY_ANIMALS ) per il nome del tipo, NON il tipo di oggetto singolare.

Ecco una funzione generica per la scrittura di array. Nel tuo caso, listType sarebbe "T_ARRAY_ANIMALS" e passeresti in 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);
}

Note:

  • Ad un certo punto ho pensato a setTypeMap era richiesto, ma ora quando rimuovo quella riga il mio codice funziona ancora, quindi non sono sicuro che sia necessario.
  • Non sono sicuro se dovresti scrivere null o un array vuoto, ma ho pensato che l'array vuoto sia più corretto.

Suggerimenti sui tipi di Oracle

  • Oracle mette tutto in maiuscolo, quindi tutti i nomi dei tipi devono essere maiuscoli.
  • Potrebbe essere necessario specificare SCHEMA.TYPE_NAME se il tipo non è nello schema predefinito.
  • Ricordati di grant execute sui tipi se l'utente con cui ti stai connettendo non è il proprietario.
    Se hai eseguito sul pacchetto, ma non sul tipo, getArray() genererà un'eccezione quando tenterà di cercare i metadati di tipo.

Primavera

Per gli sviluppatori che utilizzano Spring , potresti voler dare un'occhiata a Spring Data JDBC Extensions , che fornisce SqlArrayValue e SqlReturnArray , utili per creare un SimpleJdbcCall per una procedura che accetta un array come argomento o restituisce un array.

Capitolo 7.2.1 Impostazione dei valori ARRAY utilizzando SqlArrayValue per un parametro IN spiega come chiamare le procedure con i parametri dell'array.