2010-07-13

Spring Framework で Oracle Type を利用する

久しぶりに投稿します。

いやー、悩みました。
ようやく解決。

Oracle DB で TYPE AS OBJECT とかを使用したテーブル、ストアドなどを、Spring Framework から利用するための情報です。
ここでは引数(INパラメータ)にTYPEがあるストアドを実行する場合で説明します。
Spring の StoredProcedure を利用します。

まず、実行だけだとこんな感じで実装します。
ストアドの引数が TYPE AS OBJECT の場合


StoredProcedure sp = new StoredProcedure(dataSource, "SCHEMA.STORED_NAME");
sp.declareParameter(new SqlParameter("PARAM_NAME", Types.STRUCT, "ORACLE_TYPE_NAME"));
Map param = new HashMap();
param.put("PARAM_NAME", new OracleStructTypeValue("ORACLE_TYPE_NAME", "aaa", "bbb"));
sp.execute(param);


1行目
 Spring のStoredProcedureクラスのインスタンス化
 コンストラクタの引数は1つ目にDataSource、2つ目は実行するストアド名
2行目
 ストアドの引数の定義
 今回はINパラメータ1つでそれがTYPEだった場合。TYPE は AS OBJECT 、つまり構造体的な場合。
 StoredProcedureクラスのdeclareParameterメソッドに SqlParameter を渡す。
 SqlParameterのコンストラクタの引数は以下のように指定する
 1つ目:引数名(任意の文字列)
 2つ目:java.sql.Types.STRUCTを指定
 3つ目:Oracle 側のTYPEの名前
3行目
 実行時の引数となるMapの作成。
4行目
 ここがポイントかな。実行時引数のキーは2行目で指定したSqlParameterの1番目の引数のモノと同じにして、ポイントは値側。
 通常数値や文字列を渡すところをorg.springframework.jdbc.core.support.AbstractSqlTypeValueを実装したクラスで指定する。
 今回はOracleStructTypeValueというクラスを作成。
 内容は以下のような形。


import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;

import org.apache.commons.lang.StringUtils;
import org.springframework.jdbc.core.support.AbstractSqlTypeValue;

public class OracleStructTypeValue extends AbstractSqlTypeValue {
private String structName;
private Object[] parameterValues;

public OracleStructTypeValue(Object... args) {
parameterValues = args;
}
public OracleStructTypeValue(List args) {
parameterValues = args.toArray();
}
public OracleStructTypeValue(String structName, Object... args) {
this.structName = structName;
parameterValues = args;
}
public OracleStructTypeValue(String structName, List args) {
this.structName = structName;
parameterValues = args.toArray();
}

public void setStructName(String structName) {
this.structName = structName;
}

public void setParameterValue(Object[] parameterValues) {
this.parameterValues = parameterValues;
}

@Override
protected Object createTypeValue(Connection con, int sqlType, String typeName) throws SQLException {
StructDescriptor structDescriptor = StructDescriptor.createDescriptor(
StringUtils.isEmpty(structName) ? typeName : structName, con);
return new STRUCT(structDescriptor, con, parameterValues);
}


XMLからも定義できるようにsetterなども用意しましたが、基本はcreateTypeValueというメソッドを実装すればOK。
createTypeValueの引数の3つ目typeName は、実行時に指定したTYPE名(最初のソースで言う2行目の SqlParameterのコンストラクタ引数の3番目)が渡ってきます。基本はそれをそのままStructDescriptorcreateDescriptorの1番目の引きすに渡せばOKだと思いますが、ここでは別途指定もできるようにしています。
createDescriptorの2番目の引数は実行時にストアドに渡す具体的な値の配列。
Oracle側で定義したTYPEの上から順番に配列にして渡します。(最初のソースでは"aaa"と"bbb"という文字列を渡ています)
これで実行できます。
ただし注意点が。このcreateTypeValueメソッドの引数となっているConnectionOracleConnection型でないとClassCastExceptionが出ます。
なので、DataSourceの設定を以下のようにしてあげる必要があります。


<bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" method="close">
<property name="URL" value="jdbc:oracle:thin:@localhost:1521:orcl" />
<property name="user" value="scott" />
<property name="password" value="tiger" />
</bean>



今回はここまで。
TYPE の型が TABLE の場合とか、OUT パラメータの場合は次回以降で。(あるのか?)