Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Liest ein ARRAY aus einem STRUCT, das von einer gespeicherten Prozedur zurückgegeben wird

Erstellen Sie Objekte, die java.sql.SQLData implementieren . Erstellen Sie in diesem Szenario TEnclosure und TAnimal Klassen, die beide SQLData implementieren .

Nur zu Ihrer Information, in neueren Oracle JDBC-Versionen, Typen wie oracle .sql.ARRAY sind zugunsten von java.sql veraltet Typen. Obwohl ich nicht sicher bin, wie man ein Array (unten beschrieben) nur mit java.sql schreibt API.

Wenn Sie readSQL() implementieren Sie lesen die Felder der Reihe nach. Sie erhalten ein java.sql.Array mit sqlInput.readArray() . Also TEnclosure.readSQL() würde in etwa so aussehen.

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

Hinweis:readInt() existiert auch, aber Oracle JDBC scheint immer BigDecimal bereitzustellen für NUMBER

Sie werden feststellen, dass einige APIs wie java.sql.Array haben Methoden, die einen Typ map Map<String, Class<?>> annehmen Dies ist eine Zuordnung von Oracle-Typnamen zu ihrer entsprechenden Java-Klasse, die SQLData implementiert (ORAData kann auch funktionieren?).

Rufen Sie einfach Array.getArray() auf , erhalten Sie Struct Objekte, es sei denn, der JDBC-Treiber kennt Ihre Typzuordnungen über Connection.setTypeMap(typeMap) . Das Festlegen von typeMap für die Verbindung hat jedoch bei mir nicht funktioniert, daher verwende ich getArray(typeMap)

Erstellen Sie Ihre Map<String, Class<?>> typeMap irgendwo und fügen Sie Einträge für Ihre Typen hinzu:

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

Innerhalb eines SQLData.readSQL() Implementierung, rufen Sie sqlInput.readArray().getArray(typeMap) auf , die Object[] zurückgibt wo das Object Einträge oder vom Typ TAnimal .

Natürlich der Code zum Konvertieren in eine List<TAnimal> wird mühsam, also verwenden Sie einfach diese Utility-Funktion und passen Sie sie an Ihre Bedürfnisse an, was die Richtlinien für null vs. leere Listen betrifft:

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

Arrays schreiben

Herauszufinden, wie ein Array geschrieben wird, war frustrierend, Oracle-APIs erfordern eine Verbindung, um ein Array zu erstellen, aber Sie haben keine offensichtliche Verbindung im Kontext von writeSQL(SQLOutput sqlOutput) . Glücklicherweise dieses Blog hat einen Trick/Hack, um die OracleConnection zu bekommen , die ich hier verwendet habe.

Wenn Sie ein Array mit createOracleArray() erstellen Sie bestimmen den Listentyp (T_ARRAY_ANIMALS ) für den Typnamen, NICHT den singulären Objekttyp.

Hier ist eine generische Funktion zum Schreiben von Arrays. In Ihrem Fall listType wäre "T_ARRAY_ANIMALS" und Sie würden List<TAnimal> übergeben

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

Hinweise:

  • Irgendwann dachte ich an setTypeMap war erforderlich, aber jetzt, wenn ich diese Zeile entferne, funktioniert mein Code immer noch, also bin ich mir nicht sicher, ob es notwendig ist.
  • Ich bin mir nicht sicher, ob Sie null oder ein leeres Array schreiben sollten, aber ich bin davon ausgegangen, dass das leere Array richtiger ist.

Tipps zu Oracle-Typen

  • Oracle schreibt alles in Großbuchstaben, daher sollten alle Typnamen in Großbuchstaben geschrieben werden.
  • Möglicherweise müssen Sie SCHEMA.TYPE_NAME angeben wenn der Typ nicht in Ihrem Standardschema enthalten ist.
  • Denken Sie daran, grant execute auf Typen, wenn der Benutzer, mit dem Sie sich verbinden, nicht der Eigentümer ist.
    Wenn Sie das Paket ausgeführt haben, aber nicht den Typ, getArray() löst eine Ausnahme aus, wenn es versucht, nach Typ-Metadaten zu suchen.

Frühling

Für Entwickler, die Spring verwenden , können Sie sich Spring Data JDBC Extensions ansehen , die SqlArrayValue bereitstellt und SqlReturnArray , die zum Erstellen eines SimpleJdbcCall nützlich sind für eine Prozedur, die ein Array als Argument akzeptiert oder ein Array zurückgibt.

Kapitel 7.2.1 Setzen von ARRAY-Werten mit SqlArrayValue für einen IN-Parameter erklärt, wie Prozeduren mit Array-Parametern aufgerufen werden.