2017-03-17 13 views
3

は、3つのOracleのカスタムタイプは(簡体字)としては、以下のとおりです。オブジェクトツリーを構築ストアドプロシージャによって返される構造体からARRAYを読むデータベースで

create or replace TYPE T_ENCLOSURE AS OBJECT(
    ENCLOSURE_ID  NUMBER(32,0), 
    ENCLOSURE_NAME VARCHAR2(255 BYTE), 
    ANIMALS   T_ARRAY_ANIMALS, 

    MEMBER FUNCTION CHECK_IF_RED RETURN BOOLEAN 
); 


create or replace TYPE T_ARRAY_ANIMALS is TABLE OF T_ANIMAL; 


create or replace TYPE T_ANIMAL AS OBJECT(
    ANIMAL_ID NUMBER(32,0), 
    NUMBER_OF_HAIRS NUMBER(32,0) 
); 

と機能、

FUNCTION GET_ENCLOSURE (f_enclosure_id zoo_schema.ENCLOSURE_TABLE.ENCLOSURE_ID%TYPE) RETURN T_ENCLOSURE 
AS 
    v_ENC T_ENCLOSURE; 
    v_idx pls_integer; 

BEGIN 

    v_ENC := T_ENCLOSURE(
     f_enclosure_id, 
     NULL, 
     T_ARRAY_ANIMALS(T_ANIMAL(NULL,NULL)) 
    ); 

    SELECT ENCLOSURE_NAME 
    INTO v_ENC.ENCLOSURE_NAME 
    FROM ENCLOSURE_TABLE WHERE ENCLOSURE_ID = f_ENCLOSURE_ID; 

    SELECT 
     CAST(MULTISET(
      SELECT ANIMAL_ID, NUMBER_OF_HAIRS 
      FROM ANIMAL_TABLE 
      WHERE ENCLOSURE_ID = f_ENCLOSURE_ID 
     ) AS T_ARRAY_ANIMALS 
    ) 
    INTO v_ENC.ANIMALS 
    FROM dual; 

RETURN v_ENC; 

END; 

今度はGET_ENCLOSURE関数を呼び出して、その結果のT_ENCLOSUREオブジェクトをJavaコードに入れたいと思います。

// prepare the call 
Connection connection = MyConnectionFactory.getConnection(SOME_CONNECTION_CONFIG); 
CallableStatement stmt = connection.prepareCall("{? = call zoo_schema.zoo_utils.GET_ENCLOSURE(?)}"); 
stmt.registerOutParameter(1, OracleTypes.STRUCT, "zoo_schema.T_ENCLOSURE"); 
stmt.setInt(2, 6); // fetch data for ENCLOSURE#6 

// execute function 
stmt.executeQuery(); 

// extract the result 
Struct resultStruct = (Struct)stmt.getObject(1); // java.sql.Struct 

私は

Integer id = ((BigInteger)resultStruct.getAttributes()[0]).intValue(); // works for me 
String name = (String)resultStruct.getAttributes()[1]); // works for me 

経由IDNAMEにアクセスすることができますしかし、私は動物

resultStruct.getAttributes()[2].getClass().getCanonicalName(); // oracle.sql.ARRAY 
ARRAY arrayAnimals = (ARRAY)jdbcStruct.getAttributes()[2]; 
arrayAnimals.getArray(); // throws a java.sql.SQLException("Internal Error: Unable to resolve name") 

のリストを取得するように見えることができない私は、裁判のビットを持っていたし、ここを含むエラー

OracleConnection oracleConnection = connection.unwrap(OracleConnection.class); 
STRUCT resultOracleStruct = (STRUCT) stmt.getObject(1); // oracle.sql.STRUCT 
oracleConnection.createARRAY("zoo_schema.T_ARRAY_ANIMALS", resultOracleStruct.getAttributes()[2]) // throws an SQLException("Fail to convert to internal representation: [email protected]") 

でも運がない。

List<TAnimal>に動物のリストを取得するにはどうすればよいですか?

+0

JDBCからTABLE OF配列(plsql型)を直接または標準的に取り出す方法はないと思います。通常、Java(JDBC)からSQLオブジェクトを返すプロシージャは、PlSQlオブジェクトではなく(OBJECT型の中にラップされていても、それらを取り出すことはできません)のみコールできます。私は1)オブジェクトのテーブルを文字列に変換し(カンマなどで区切って)、クライアントで手動で解析する2)/questions/37767761/plsql-how-to-return-associative-array-to-java?rq = 1) –

+0

非常にオラクル固有のソリューションはまったく問題ありません。このプロジェクトではデータベースは変更されません。 – DerMike

+0

GET_ENCLOSUREをデバッグするコードを提供できますか? –

答えて

0

Oracle固有の解決策があれば、鍵はDTO内にあります。それらのすべてがORADataORADataFactory

public class TAnimal implements ORAData, ORADataFactory { 
    Integer animal_id, number_of_hairs; 

    public TAnimal() { } 

    // [ Getter and Setter omitted here ] 

    @Override 
    public Datum toDatum(Connection connection) throws SQLException { 
     OracleConnection oracleConnection = connection.unwrap(OracleConnection.class); 
     StructDescriptor structDescriptor = StructDescriptor.createDescriptor("zoo_schema.T_ANIMAL", oracleConnection); 
     Object[] attributes = { 
       this.animal_id, 
       this.number_of_hairs 
     }; 
     return new STRUCT(structDescriptor, oracleConnection, attributes); 
    } 

    @Override 
    public TAnimal create(Datum datum, int sqlTypeCode) throws SQLException { 
     if (datum == null) { 
      return null; 
     } 
     Datum[] attributes = ((STRUCT) datum).getOracleAttributes(); 
     TAnimal result = new TAnimal(); 
     result.animal_id = asInteger(attributes[0]); // see TEnclosure#asInteger(Datum) 
     result.number_of_hairs = asInteger(attributes[1]); // see TEnclosure#asInteger(Datum) 
     return result; 
    } 

} 

public class TEnclosure implements ORAData, ORADataFactory { 

    Integer enclosureId; 
    String enclosureName; 
    List<Animal> animals; 

    public TEnclosure() { 
     this.animals = new ArrayList<>(); 
    } 

    // [ Getter and Setter omitted here ] 

    @Override 
    public Datum toDatum(Connection connection) throws SQLException { 
     OracleConnection oracleConnection = connection.unwrap(OracleConnection.class); 
     StructDescriptor structDescriptor = StructDescriptor.createDescriptor("zoo_schema.T_ENCLOSURE", oracleConnection); 
     Object[] attributes = { 
       this.enclosureId, 
       this.enclosureName, 
       null // TODO: solve this; however, retrieving data works without this 
     }; 
     return new STRUCT(structDescriptor, oracleConnection, attributes); 
    } 

    @Override 
    public TEnclosure create(Datum datum, int sqlTypeCode) throws SQLException { 
     if (datum == null) { 
      return null; 
     } 
     Datum[] attributes = ((STRUCT) datum).getOracleAttributes(); 
     TEnclosure result = new TEnclosure(); 
     result.enclosureId = asInteger(attributes[0]); 
     result.enclosureName = asString(attributes[1]); 
     result.animals = asListOfAnimals(attributes[2]); 
     return result; 
    } 

    // Utility methods 

    Integer asInteger(Datum datum) throws SQLException { 
     if (datum == null) 
      return null; 
     else 
      return ((NUMBER) datum).intValue(); // oracle.sql.NUMBER 
    } 

    String asString(Datum datum) throws SQLException { 
     if (datum = null) 
      return null; 
     else 
      return ((CHAR) datum).getString(); // oracle.sql.CHAR 
    } 

    List<TAnimal> asListOfAnimals(Datum datum) throws SQLException { 
     if (datum == null) 
      return null; 
     else { 
      TAnimal factory = new TAnimal(); 

      List<TAnimal> result = new ArrayList<>(); 

      ARRAY array = (ARRAY) datum; // oracle.sql.ARRAY 
      Datum[] elements = array.getOracleArray(); 
      for (int i = 0; i < elements.length; i++) { 
       result.add(factory.create(elements[i], 0)); 
      } 
      return result; 
     } 
    } 
} 

を実装する必要があり、その後、データをフェッチすることはそれほどのように動作します:

TEnclosure factory = new TEnclosure(); 

    Connection connection = null; 
    OracleConnection oracleConnection = null; 
    OracleCallableStatement oracleCallableStatement = null; 

    try { 
     connection = MyConnectionFactory.getConnection(SOME_CONNECTION_CONFIG); 
     oracleConnection = connection.unwrap(OracleConnection.class); 
     oracleCallableStatement = (OracleCallableStatement) oracleConnection.prepareCall("{? = call zoo_schema.zoo_utils.GET_ENCLOSURE(?)}"); 

     oracleCallableStatement.registerOutParameter(1, OracleTypes.STRUCT, "zoo_schema.T_ENCLOSURE"); 
     oracleCallableStatement.setInt(2, 6); // fetch data for ENCLOSURE#6 

     // Execute query 
     oracleCallableStatement.executeQuery(); 

     // Check result 
     Object oraData = oracleCallableStatement.getORAData(1, factory); 
     LOGGER.info("oraData is a {}", oraData.getClass().getName()); // acme.zoo.TEnclosure 

    } finally { 
     ResourceUtils.closeQuietly(oracleCallableStatement); 
     ResourceUtils.closeQuietly(oracleConnection); 
     ResourceUtils.closeQuietly(connection); // probably not necessary... 
    } 
1

java.sql.SQLDataを実装するオブジェクトを作成します。このシナリオでは、を実装するTEnclosureクラスとTAnimalクラスを作成します。

最新のOracle JDBCバージョンでは、oracle.sql.ARRAYなどの型は非推奨になり、java.sql型が使用されます。 java.sql APIのみを使用して配列を記述する方法(以下を参照)はわかりませんが。

readSQL()を実装すると、フィールドが順番に読み込まれます。 でjava.sql.Arrayを取得します。したがって、TEnclosure.readSQL()はこのようになります。

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

注:readInt()も存在しますが、Oracle JDBCは常にあなたはこのようなjava.sql.Arrayなどの一部のAPIは、型マップMap<String, Class<?>>これを取る方法を持っていることがわかりますNUMBER

ためBigDecimalを提供するようだオラクルのマッピングです対応するJavaクラスにSQLDataORADataも有効です)を実装しています。

Array.getArray()を呼び出すと、JDBCドライバがConnection.setTypeMap(typeMap)を介して型マッピングについて知っていない限り、Structオブジェクトが返されます。ただし、接続にTYPEMAP設定すると、私のために働くので、私はgetArray(typeMap)

はどこかMap<String, Class<?>> typeMapを作成し、あなたの種類のエントリを追加使用していませんでした:

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

SQLData.readSQL()実装内で、sqlInput.readArray().getArray(typeMap)を呼び出し、返しますObject[]ObjectのエントリまたはTAnimalのエントリ。もちろん

、面倒なので、ちょうどこのユーティリティ関数を使用して、空のリストポリシー対としてnull限り、あなたのニーズに合わせてそれを調整しますList<TAnimal>に変換するためのコード:

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

ライティング配列

アレイの書き込み方法を理解するのは難解でした.Oracle APIでは配列を作成するにはConnectionが必要ですが、コンテキストには明らかな接続がありませんwriteSQL(SQLOutput sqlOutput)。幸いなことに、this blogにはOracleConnectionを取得するためのトリック/ハックがあります。あなたはcreateOracleArray()で配列を作成するとき

あなたはタイプ名のリストタイプ(T_ARRAY_ANIMALS)、NOT単数オブジェクト型を指定し

ここに、配列を書き込むための汎用関数があります。あなたのケースでは、listType"T_ARRAY_ANIMALS"だろうとあなたが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); 
} 

ノートに渡します。ある時点で

  • 、私はsetTypeMapが必要だったと思ったが、私はその行を削除したときに、今私のコードはまだ動作しますそれが必要かどうかは分かりません。
  • nullまたは空の配列を記述する必要があるかどうかはわかりませんが、空の配列が正しいと仮定しました。 Oracle型の

ヒント

  • Oracleは、すべてを大文字にするので、すべてのタイプ名は大文字でなければなりません。
  • タイプがデフォルトスキーマにない場合は、SCHEMA.TYPE_NAMEを指定する必要があります。
  • 接続しているユーザーが所有者でない場合は、タイプについてgrant executeを覚えておいてください。
    パッケージでは実行しているが型は実行していない場合、getArray()は型のメタデータを探すときに例外をスローします。

使用する開発者のために

は、あなたはとして配列を受け取り手続きのためSimpleJdbcCallを作成するのに便利ですSqlArrayValueSqlReturnArrayを、提供され、Spring Data JDBC Extensionsを見てみたいことがあり引数を返すか、配列を返します。

7.2.1 Setting ARRAY values using SqlArrayValue for an IN parameterでは、配列パラメータでプロシージャを呼び出す方法について説明しています。

関連する問題