在JAVA程序中訪問具有數組入參的Oracle存儲過程,必須通過java.sql.Array來實現。通過查看java.sql.Array的源代碼
package java.sql;
/**
* The mapping in the Java programming language for the SQL type
* <code>ARRAY</code>.
* By default, an <code>Array</code> value is a transaction-duration
* reference to an SQL <code>ARRAY</code> value. By default, an <code>Array</code>
* object is implemented using an SQL LOCATOR(array) internally, which
* means that an <code>Array</code> object contains a logical pointer
* to the data in the SQL <code>ARRAY</code> value rather
* than containing the <code>ARRAY</code> value's data.
* <p>
* The <code>Array</code> interface provides methods for bringing an SQL
* <code>ARRAY</code> value's data to the client as either an array or a
* <code>ResultSet</code> object.
* If the elements of the SQL <code>ARRAY</code>
* are a UDT, they may be custom mapped. To create a custom mapping,
* a programmer must do two things:
* <ul>
* <li>create a class that implements the {@link SQLData}
* interface for the UDT to be custom mapped.
* <li>make an entry in a type map that contains
* <ul>
* <li>the fully-qualified SQL type name of the UDT
* <li>the <code>Class</code> object for the class implementing
* <code>SQLData</code>
* </ul>
* </ul>
* <p>
* When a type map with an entry for
* the base type is supplied to the methods <code>getArray</code>
* and <code>getResultSet</code>, the mapping
* it contains will be used to map the elements of the <code>ARRAY</code> value.
* If no type map is supplied, which would typically be the case,
* the connection's type map is used by default.
* If the connection's type map or a type map supplied to a method has no entry
* for the base type, the elements are mapped according to the standard mapping.
* <p>
* @since 1.2
*/
public interface Array {
...
}
可以明顯看到,Array的實現時通過SQL LOCATOR。通過JDBC調用時,是使用指向數組的指針來實現,而不是複製一份拷貝。類似的類型還有BLOB,CLOB等大對象處理,在調用時,僅維護一個引用。維護指向數據庫實際類型的指針,比較類似的用法是java.sql.ResultSet。爲了獲取到實際的指針引用,應用程序需要維護一個數據庫的物理連接。
在實際應用中,尤其是基於Spring的應用中,優先採用數據庫連接池來操作數據庫,而不是JDBC只連。Spring的參考配置:
<bean id="devDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>oracle.jdbc.driver.OracleDriver</value> </property> <property name="url"> <value>jdbc:oracle:thin:@10.240.14.10:1521:p09tr</value> </property> <property name="username"> <value>test</value> </property> <property name="password"> <value>test</value> </property> <property name="defaultAutoCommit"> <value>false</value> </property> <property name="maxActive"> <value>5</value> </property> <property name="accessToUnderlyingConnectionAllowed"> <value>true</value> </property> </bean> <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>mm_datasource</value> </property> <property name="jndiEnvironment"> <props> <prop key="java.naming.provider.url"> t3://10.240.14.1:7100 </prop> <prop key="java.naming.factory.initial"> weblogic.jndi.WLInitialContextFactory </prop> </props> </property> <property name="defaultObject" ref="devDataSource" /> </bean> <bean id="sessionFactory" class="com.fenet.insurance.core.spring.hibernate3.annotation.ScanAnnotationSessionFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> </property> <property name="annotatedScanPackages"> <list> <value>com.fenet.insurance.mm.study.hibernate.id.entity</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.Oracle9Dialect </prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.jdbc.batch_size">5</prop> </props> </property> </bean> <bean id="oracleTypeHandler" class="ccom.fenet.insurance.mm.common.oracle.OracleTypeHandlerImpl"> <property name="dataSource"> <ref local="dataSource" /> </property> </bean>
優先通過JNDI獲取連接池,如果沒有取到則,默認用dbcp的連接池。通過DataSource訪問數據庫,用到的連接是邏輯連接,而非實際連接數據庫的物理連接,僅僅是物理的連接的一個Wrapper。在本文中討論,在Spring應用中調用具有Array參數的Oracle存儲過程,在創建Array時需要用到實際的物理連接。
假設,我們使用的Oracle表和數組類型爲
create table study_array_nick_tab
(
name varchar2(200)
);
create or replace type study_array_nick_list is VARRAY(1000) of varchar2(200);
被調用的Oracle存儲過程爲
create or replace procedure study_array_nick(in_array in study_array_nick_list) is
v_i number;
begin
for v_i in 1 .. in_array.count loop
insert into study_array_nick_tab(name) values(in_array(v_i));
end loop;
commit;
exception when others then
rollback;
raise_application_error('20999','測試錯誤');
end study_array_nick;
在實際生成Array時,
public static <D> ARRAY createARRAY(DataSource dataSource,
String arrayname, D[] objects) {
Assert.notNull(dataSource, "創建Array失敗,傳入參數dataSource爲空.");
Assert.hasText(arrayname, "創建Array失敗,傳入參數arrayname爲空.");
Assert.notEmpty(objects, "創建Array失敗,傳入參數objects爲空.");
Connection poolConn = null;
Connection vendorConn = null;
try {
poolConn = dataSource.getConnection();
if(poolConn instanceof DelegatingConnection) {
vendorConn = (OracleConnection) ((PoolableConnection) ((DelegatingConnection) poolConn).getDelegate()).getDelegate();
} else {
vendorConn = ((WLConnection) poolConn).getVendorConnection(); }
ArrayDescriptor arrayDescriptor = ArrayDescriptor.createDescriptor(
arrayname, vendorConn);
return new ARRAY(arrayDescriptor, vendorConn, objects);
} catch (SQLException e) {
throw new BusinessException("創建Array失敗.", e);
} finally {
vendorConn = null;
try {
if (poolConn != null && !poolConn.isClosed()) {
poolConn.close();
}
} catch (SQLException e) {
throw new BusinessException("創建Array,關閉連接失敗.", e);
}
}
}
使用完連接後,儘量只關閉池邏輯連接,對於物理連接不應該關閉,而是交給池去管理。得到Array後,就可以調用包含此Array參數的存儲過程。
DataSource ds = bean.getDataSource();
JdbcTemplate jdbc = new JdbcTemplate(ds);
// PlatformTransactionManager tm = new DataSourceTransactionManager(ds);
// TransactionStatus status = tm.getTransaction(null);
jdbc.execute(new CallableStatementCreator() {
public CallableStatement createCallableStatement(Connection con)
throws SQLException {
return con.prepareCall("{call study_array_nick(?)}");
}
}, new CallableStatementCallback() {
public Object doInCallableStatement(CallableStatement cs)
throws SQLException, DataAccessException {
ARRAY oa = bean.createARRAY("STUDY_ARRAY_NICK_LIST",
new String[] { "666", "7777", "7775" });
Assert.notNull(oa);
cs.setArray(1, oa);
cs.execute();
return null;
}
});
// tm.commit(status);
在實際使用中發生了很多問題,首先對於不同連接池,獲取物理連接的方式不一致。另外,在非Weblogic環境下使用Weblogic連接池來生成Oracle的ARRAY時,調用getVendorConnection時會出現序列化錯誤。經查實後,發現Weblogic中的JDBC操作的序列化,會由容器通過特別的類來中轉(比如說SerailConnection)。