Spring訪問傳入數組參數的Oracle存儲過程

在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)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章