Oracle
 sql >> Base de données >  >> RDS >> Oracle

Lire un ARRAY à partir d'un STRUCT renvoyé par une procédure stockée

Créer des objets qui implémentent java.sql.SQLData . Dans ce scénario, créez TEnclosure et TAnimal classes, qui implémentent toutes deux SQLData .

Juste pour info, dans les nouvelles versions d'Oracle JDBC, des types tels que oracle .sql.TABLEAU sont obsolètes au profit de java.sql les types. Bien que je ne sois pas sûr de savoir comment écrire un tableau (décrit ci-dessous) en utilisant uniquement java.sql API.

Lorsque vous implémentez readSQL() vous lisez les champs dans l'ordre. Vous obtenez un java.sql.Array avec sqlInput.readArray() . Donc TEnclosure.readSQL() ressemblerait à ceci.

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

Remarque :readInt() existe également, mais Oracle JDBC semble toujours fournir BigDecimal pour NUMBER

Vous remarquerez que certaines API telles que java.sql.Array avoir des méthodes qui prennent une carte de type Map<String, Class<?>> Il s'agit d'un mappage des noms de type Oracle à leur classe Java correspondante implémentant SQLData (ORAData peut fonctionner aussi ?).

Si vous appelez simplement Array.getArray() , vous obtiendrez Struct objets sauf si le pilote JDBC connaît vos mappages de types via Connection.setTypeMap(typeMap) . Cependant, la définition de typeMap sur la connexion n'a pas fonctionné pour moi, j'utilise donc getArray(typeMap)

Créez votre Map<String, Class<?>> typeMap quelque part et ajoutez des entrées pour vos types :

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

Dans un SQLData.readSQL() implémentation, appelez sqlInput.readArray().getArray(typeMap) , qui renvoie Object[] où l'Object entrées ou de type TAnimal .

Bien sûr le code à convertir en List<TAnimal> devient fastidieux, alors utilisez simplement cette fonction utilitaire et ajustez-la à vos besoins en ce qui concerne la politique de liste nulle ou vide :

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

Écrire des tableaux

Comprendre comment écrire un tableau était frustrant, les API Oracle nécessitent une connexion pour créer un tableau, mais vous n'avez pas de connexion évidente dans le contexte de writeSQL(SQLOutput sqlOutput) . Heureusement, ce blog a un truc/hack pour obtenir le OracleConnection , que j'ai utilisé ici.

Lorsque vous créez un tableau avec createOracleArray() vous spécifiez le type de liste (T_ARRAY_ANIMALS ) pour le nom du type, PAS le type d'objet singulier.

Voici une fonction générique pour écrire des tableaux. Dans votre cas, listType serait "T_ARRAY_ANIMALS" et vous passeriez dans 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);
}

Remarques :

  • À un moment donné, j'ai pensé setTypeMap était nécessaire, mais maintenant, lorsque je supprime cette ligne, mon code fonctionne toujours, donc je ne sais pas si c'est nécessaire.
  • Je ne sais pas si vous devez écrire null ou un tableau vide, mais j'ai supposé que le tableau vide est plus correct.

Conseils sur les types Oracle

  • Oracle met tout en majuscules, donc tous les noms de type doivent être en majuscules.
  • Vous devrez peut-être spécifier SCHEMA.TYPE_NAME si le type n'est pas dans votre schéma par défaut.
  • N'oubliez pas d'grant execute sur les types si l'utilisateur avec lequel vous vous connectez n'est pas le propriétaire.
    Si vous avez exécuté sur le package, mais pas sur le type, getArray() lèvera une exception lorsqu'il essaiera de rechercher des métadonnées de type.

Printemps

Pour les développeurs utilisant Spring , vous pouvez consulter Spring Data JDBC Extensions , qui fournit SqlArrayValue et SqlReturnArray , qui sont utiles pour créer un SimpleJdbcCall pour une procédure qui prend un tableau comme argument ou retourne un tableau.

Chapitre 7.2.1 Définition des valeurs ARRAY à l'aide de SqlArrayValue pour un paramètre IN explique comment appeler des procédures avec des paramètres de tableau.