2017-03-17 38 views
3

在數據庫中三個Oracle定製類型(簡化的)如下:閱讀來自通過存儲過程返回一個STRUCT的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函數,並在我的Java代碼中使用它的結果T_ENCLOSURE對象。

// 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類型中,您將無法檢索它們)。我看到2個可能的解決方案,但1)將你的對象錶轉換爲一個字符串(用逗號等分隔)並在客戶端手動解析它2)嘗試使用一些特定於Oracle的未公開的API(例如http://stackoverflow.com/questions/37767761/plsql-how-to-return-associative-array-to-java?rq = 1) –

+0

一個非常特定於Oracle的解決方案將會非常好。該項目中的數據庫不會被更改。 – 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對象。在這種情況下,創建TEnclosureTAnimal類,它們都實現了SQLData

只是在FYI中,在較新的Oracle JDBC版本中,類型(例如oracle.sql.ARRAY)已被棄用,而傾向於使用java.sql類型。儘管我不確定如何僅使用java.sql API編寫數組(如下所述)。

當您執行readSQL()時,您按順序讀取字段。您使用sqlInput.readArray()獲得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()也存在,但甲骨文JDBC似乎總是NUMBER

提供BigDecimal你會發現,一些API,比如java.sql.Array有坐式地圖Map<String, Class<?>>此方法是Oracle的映射鍵入名稱到其相應的Java類實現SQLDataORAData也可能工作?)。

如果您只是致電Array.getArray(),您將獲得Struct對象,除非JDBC驅動程序通過Connection.setTypeMap(typeMap)知道您的類型映射。然而,在連接上設置的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

當然的代碼轉換爲List<TAnimal>被繁瑣,所以只用這個效用函數,並調整它爲您的需求,只要空VS空單策略:

/** 
* 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)的上下文中沒有明顯的Connection。幸運的是,this blog有一個技巧/破解得到OracleConnection,我在這裏使用。

當你創建一個數組createOracleArray()指定類型名稱,而不是單一對象類型列表類型T_ARRAY_ANIMALS)。

下面是寫入數組的一個通用函數。在你的情況,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是必需的,但現在當我刪除了這一行我的代碼仍然有效,所以我不確定是否有必要。
  • 我不確定如果你應該寫空或數組,但我認爲空數組更正確。在Oracle類型

    • 甲骨文都轉換爲大寫,

    提示,因此所有類型的名稱應爲大寫。

  • 如果類型不在默認模式中,您可能需要指定SCHEMA.TYPE_NAME
  • 如果您要連接的用戶不是所有者,請記住grant execute的類型。
    如果您對包執行了操作,但沒有執行類型,則getArray()將在嘗試查找類型元數據時引發異常。

對於使用開發人員,你可能想看看Spring Data JDBC Extensions,它提供SqlArrayValueSqlReturnArray,這是該接受一個數組作爲一個程序創建一個SimpleJdbcCall有用參數或返回一個數組。

章節7.2.1 Setting ARRAY values using SqlArrayValue for an IN parameter解釋瞭如何使用數組參數調用過程。