谷本 心 in せろ部屋 このページをアンテナに追加 RSSフィード

2007-03-26

[][]ストアドプロシージャの戻り値DTOで取得するには?

ストアドプロシージャの戻り値Dtoで返すためには、

の2段階が必要。


似たような処理をしているのが

BeanMetaDataとか、AbstractBeanMetaDataResultSetHandlerとなので、

この辺りを参考にして作れそう。


ただ、通常のTableと比べて、

ストアドプロシージャの戻り値はシンプルなので、

実装としては、もうちょっと簡単になるかな。


作るべきクラスは、こんな感じ。

  • ProcedureResultMetaData
  • ProcedureResultHandler
  • STRUCTを扱うクラス
  • ARRAYを扱うクラス

STRUCTを扱うクラスは、MetaDataにするか、ValueTypeが良いか悩む所。

  • StructMetaData
  • StructType implements ValueType

両方作ることになるかも知れないけど、

今の所、あまりソースを読んでないので、決定できず。


とにかく、まずはProcedureResultMetaDataを作って

ストアドプロシージャの戻り値DTOで受けられるようにしてから、

STRUCTとDTOマッピングを行なおう。

2007-03-25

[]STRUCTの詳細はOracleTypeADTを使って取る。

こんなコードを書くと、

StructDescriptor sd = new StructDescriptor("PERSON", conn);
OracleTypeADT adt = sd.getOracleTypeADT();

int attrNum = adt.getNumAttrs();
for (int i = 1; i <= attrNum; i++) {
	String name = adt.getAttributeName(i);
	System.out.println(name);
}

こんな結果が取れる

ID
NAME

こんな風に、OracleTypeADTを使えば、STRUCTの属性一覧を取得できる。

[][]引数のSTRUCTやARRAYに対応する方法

S2Daoのストアド呼び出しの引数に、STRUCTやARRAYを使えるようにする改修。


  • ストアドプロシージャの引数がSTRUCT
    • Dao側のメソッドは、引数Dtoにする
  • ストアドプロシージャの引数がARRAY
    • Dao側のメソッドは、引数をListか配列にする

、、、を実現するための方法。


いまの所、S2DaoのProcedureHandlerは変更に対して開放されてないので

org.seasar.dao.handler.AbstractBasicProcedureHandlerを直接いじるか、

org.seasar.dao.handler.ProcedureHandlerImplでオーバーライドする必要がある。


いじる対象は、bindArgs。

現在 (s2-dao 1.0.40) の実装はこんな感じ。

protected void bindArgs(CallableStatement ps, Object[] args)
		throws SQLException {
	if (args == null) {
		return;
	}
	int argPos = 0;
	for (int i = 0; i < columnTypes.length; i++) {
		if (isOutputColum(columnInOutTypes[i].intValue())) {
			ps.registerOutParameter(i + 1, columnTypes[i].intValue());
		}
		if (isInputColum(columnInOutTypes[i].intValue())) {
			ps.setObject(i + 1, args[argPos++], columnTypes[i].intValue());
		}
	}
}

これを、こんな風に変更する。

protected void bindArgs(CallableStatement ps, Object[] args)
		throws SQLException {
	if (args == null) {
		return;
	}
	int argPos = 0;
	for (int i = 0; i < columnTypes.length; i++) {
		if (isOutputColum(columnInOutTypes[i].intValue())) {
			ps.registerOutParameter(i + 1, columnTypes[i].intValue(),
					typeNames[i]);
		}
		if (isInputColum(columnInOutTypes[i].intValue())) {
			Object arg = args[argPos++];

			// ここで条件判定。STRUCTかARRAYならラッピング。
			if (columnTypes[i].intValue() == Types.STRUCT) {
				arg = new StructWrapper(arg, typeNames[i]);
			} else if (columnTypes[i].intValue() == Types.ARRAY) {
				arg = new ArrayWrapper(arg, typeNames[i]);
			}

			ps.setObject(i + 1, arg, columnTypes[i].intValue());
		}
	}
}

で、ラッパーを2クラス追加する。

import java.sql.Connection;
import java.sql.SQLException;

import oracle.jdbc.oracore.OracleTypeADT;
import oracle.sql.Datum;
import oracle.sql.ORAData;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;

import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;

public class StructWrapper implements ORAData {
	private Object bean;

	private String typeName;

	private BeanDesc desc;

	public StructWrapper(Object bean, String typeName) {
		this.bean = bean;
		this.typeName = typeName;
		this.desc = BeanDescFactory.getBeanDesc(bean.getClass());
	}

	public Datum toDatum(Connection conn) throws SQLException {
		StructDescriptor sd = new StructDescriptor(typeName, conn);
		String[] attrNames = getAttributeNames(sd);

		Object[] args = new Object[attrNames.length];
		for (int i = 0; i < attrNames.length; i++) {
			args[i] = desc.getFieldValue(attrNames[i], bean);
		}

		return new STRUCT(sd, conn, args);
	}

	private String[] getAttributeNames(StructDescriptor sd) throws SQLException {
		OracleTypeADT adt = sd.getOracleTypeADT();
		int attrNum = adt.getNumAttrs();

		String[] fieldNames = new String[attrNum];
		for (int i = 0; i < attrNum; i++) {
			fieldNames[i] = adt.getAttributeName(i + 1).replaceAll("_", "")
					.toLowerCase();
		}

		return fieldNames;
	}
}
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collection;

import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import oracle.sql.Datum;
import oracle.sql.ORAData;

public class ArrayWrapper implements ORAData {
	private Object[] array;

	private String typeName;

	public ArrayWrapper(Object collection, String typeName) {
		if (collection.getClass().isArray()) {
			array = (Object[]) collection;
		} else if (collection instanceof Collection) {
			array = ((Collection) collection).toArray();
		} else {
			throw new IllegalArgumentException(
					"Argument must be array or collection");
		}

		this.typeName = typeName;
	}

	public Datum toDatum(Connection conn) throws SQLException {
		ArrayDescriptor ad = new ArrayDescriptor(typeName, conn);

		Object[] arg = null;
		if (ad.getBaseType() == Types.STRUCT) {
			arg = new Object[array.length];

			for (int i = 0; i < array.length; i++) {
				arg[i] = new StructWrapper(array[i], ad.getBaseName());
			}
		} else {
			arg = array;
		}

		return new ARRAY(ad, conn, arg);
	}
}

ホントはAbstractBasicProcedureHandler#initTypesにも

ちょっと処理を追加してるんだけど、根本の部分はこんな感じ。


これで、Dto配列引数にして、STRUCTやARRAYが入った

ストアドプロシージャ呼び出しができるようになりました。


[][]次は戻り値の扱い

ラッパーを作ることで、引数DTO(STRUCT)、配列(ARRAY)には対応ができた。

次は、戻り値の変換をどうするか。


ストアドプロシージャのreturnやOUTパラメータにSTRUCTやARRAYが入っていると、

Daoの戻り値にも、oracle.sql.STRUCT型や、oracle.sql.ARRAY型のオブジェクトが入ってくる。

これを、そのままにして、Logic側でうまく処理しろというのは、ちょっと酷。


STRUCTだったらDTOに変換してあげたいんだけど、

問題は、どのDTOに変換すべきか、(S2Daoには)分からない、ということ。


今のS2Daoのストアド呼び出しは戻り値がMapになっているせいで、

何でも入るんだけど、逆に言うと、何を入れれば良いか分からない。


その対策方法の候補は下の二つ。


  • STRUCTの型名と、Dtoの型名の命名規則を決めておいて、規則通りのコンポーネント定義をS2コンテナから取得する。
  • Daoメソッドの戻り値をMapだけではなく、Dtoも許可する。Dtoだった場合、Dtoに定義された型に変換して返す。

前者は、まぁ必要に応じてやるとして、

自分としてやりたいのは後者。


こんなDTOを作って、、、

public class XxxDto {
	private String returnValue;

	private Xxx xx;

	private List xxxArray;
}

Dao側での呼び出しを、こんな風にする。

public String getXxx_PROCEDURE = "xxx";

public XxxDto getXxx(Integer id1, String name1, Integer id2, String name2);

これで、戻り値をXxxDtoにマッピングして返してやる。

いいじゃないですか、これで。

2007-03-24

[]DatabaseMetaDataでドコまで取れるか、何が取れないのか

昨日のエントリーで、ちょっと嘘ついちゃいました。

ユーザ定義の詳細は、DatabasetMetaDataの別メソッドを使えば、

もうちょっと取れます。


ということで、

DatabaseMetaDataでどんな情報が取れるか見ていこう。


こんなコードを書いてみる。

public class DmdTest {
	private static final String PATH = "j2ee.dicon";

	public static void main(String[] args) throws Exception {
		S2Container container = S2ContainerFactory.create(PATH);
		container.init();

		DataSource ds = (DataSource) container.getComponent(DataSource.class);
		Connection conn = ds.getConnection();

		DatabaseMetaData dmd = ConnectionUtil.getMetaData(conn);
		ResultSet rs = dmd.getProcedures(null, "接続ユーザ名", "%");

		printResultSet(rs);
	}

	private static void printResultSet(ResultSet rs) throws SQLException {
		ResultSetMetaData rsMetaData = rs.getMetaData();
		int columnSize = rs.getMetaData().getColumnCount();

		StringBuffer buf = new StringBuffer("|*Column Names");
		for (int i = 1; i < columnSize; i++) {
			buf.append("|*" + rsMetaData.getColumnName(i));
		}
		System.out.println(buf.append("|"));

		while (rs.next()) {
			buf = new StringBuffer("|").append(rs.getRow());
			for (int i = 1; i < columnSize; i++) {
				buf.append("|" + rs.getObject(i));
			}
			System.out.println(buf.append("|"));
		}
	}
}

printResultSetは、ResultSetの内容をはてなのテーブル形式で、標準出力に表示するだけのメソッド。


まずは、DatabaseMetaData#getProceduresで取れる値。

ResultSet rs = dmd.getProcedures(null, "接続ユーザ", "%");
Column NamesPROCEDURE_CATPROCEDURE_SCHEMPROCEDURE_NAMENULLNULLNULLREMARKS
1null接続ユーザ名NEW_PERSONnullnullnullStandalone procedure or function
2null接続ユーザ名NEW_PERSON_ARRAYnullnullnullStandalone procedure or function
3null接続ユーザ名PERSON_NAMEnullnullnullStandalone procedure or function
4null接続ユーザ名PERSON_NAME_MERGEnullnullnullStandalone procedure or function
5null接続ユーザ名SALES_TAXnullnullnullStandalone procedure or function
6null接続ユーザ名SALES_TAX2nullnullnullStandalone procedure or function
7null接続ユーザ名SALES_TAX3nullnullnullStandalone procedure or function
8null接続ユーザ名SALES_TAX4nullnullnullStandalone procedure or function

特に問題ないですね。


続いて、DatabaseMetaData#getColumnsで取れる値。

ResultSet rs = dmd.getProcedureColumns(null, "接続ユーザ名", "%", null);
Column NamesPROCEDURE_CATPROCEDURE_SCHEMPROCEDURE_NAMECOLUMN_NAMECOLUMN_TYPEDATA_TYPETYPE_NAMEPRECISIONLENGTHSCALERADIXNULLABLEREMARKSSEQUENCEOVERLOAD
1null接続ユーザ名NEW_PERSONnull51111接続ユーザ名.PERSONnullnullnull101null1null
2null接続ユーザ名NEW_PERSONID13NUMBER2222null101null2null
3null接続ユーザ名NEW_PERSONNAME112VARCHAR2nullnullnull101null3null
4null接続ユーザ名NEW_PERSON_ARRAYnull51111VARRAYnullnullnull101null1null
5null接続ユーザ名NEW_PERSON_ARRAYnull41111接続ユーザ名.PERSONnullnullnull101null2null
6null接続ユーザ名NEW_PERSON_ARRAYID113NUMBER2222null101null3null
7null接続ユーザ名NEW_PERSON_ARRAYNAME1112VARCHAR2nullnullnull101null4null
8null接続ユーザ名NEW_PERSON_ARRAYID213NUMBER2222null101null5null
9null接続ユーザ名NEW_PERSON_ARRAYNAME2112VARCHAR2nullnullnull101null6null
10null接続ユーザ名PERSON_NAMEnull512VARCHAR2nullnullnull101null1null
11null接続ユーザ名PERSON_NAMEUSER11111接続ユーザ名.PERSONnullnullnull101null2null
12null接続ユーザ名PERSON_NAME_MERGEnull512VARCHAR2nullnullnull101null1null
13null接続ユーザ名PERSON_NAME_MERGEUSERS11111VARRAYnullnullnull101null2null
14null接続ユーザ名PERSON_NAME_MERGEnull11111接続ユーザ名.PERSONnullnullnull101null3null
15null接続ユーザ名SALES_TAXSALES13NUMBER2222null101null1null
16null接続ユーザ名SALES_TAXTAX43NUMBER2222null101null2null
17null接続ユーザ名SALES_TAX2null53NUMBER2222null101null1null
18null接続ユーザ名SALES_TAX2SALES13NUMBER2222null101null2null
19null接続ユーザ名SALES_TAX3SALES23NUMBER2222null101null1null
20null接続ユーザ名SALES_TAX4SALES13NUMBER2222null101null1null
21null接続ユーザ名SALES_TAX4TAX43NUMBER2222null101null2null
22null接続ユーザ名SALES_TAX4TOTAL43NUMBER2222null101null3null

型がユーザ定義型 (STRUCT) の所は、TYPE_NAMEが「接続ユーザ名.PERSON」と問題なく取れてるけど、

配列 (VARRAY) はTYPE_NAMEが全部「VARRAY」となってて、期待する「接続ユーザ名.PERSON_ARRAY」が取れない。

うーん。。。


一応、DatabaseMetaData#getUDTsで詳細も見てみる。

ResultSet rs = dmd.getUDTs("%", "接続ユーザ名", "%", null);
Column NamesTYPE_CATTYPE_SCHEMTYPE_NAMECLASS_NAMEDATA_TYPE
1null接続ユーザ名PERSONnullSTRUCT

DATA_TYPEもちゃんと取れて問題なし。

[]DatabaseMetaDataで取れないのは、ARRAYの型名

上のテーブルをよく見れば分かるけど(誰も見ないって!)、

引数戻り値配列が使われている所は、

全部「VARRAY」になっていて、その次の行に、配列の型が書いてある。


DatabaseMetaDataでは、、、

  • VARRAYの型名は取れない
  • VARRAYがどの型で構成されているかは取れる
  • STRUCTの構成までは取れない

ということ。


ArrayDescriptorを作成する際に型名は必須だから、

やっぱり自前でSQLを書いて、ディクショナリから値を取るしかないかな、と。

[][]ここまでのまとめ

  • 引数のSTRUCT
    • ORADataのラッパーを使って、DTOをラッピングして渡す。
    • StructDescriptorを利用する。
  • 戻り値のSTRUCT
    • StructをDtoに変換して返す。
    • DTOのクラス名の取り方は?
  • 引数のARRAY
    • ORADataのラッパーを使って、ラッピングして渡す。
    • ArrayDescriptorを利用する。
    • ArrayDescriptorの作成に必要なデータ型名は、DBMSからSQLで取得する?
  • 戻り値のARRAY
    • ArrayをListに変換して返す。

赤文字が、まだ迷っている所。

実案件では、STRUCTとDTOの命名規則を決めて対応したんですが、

公開するためには、それでは厳しそうなので、ちょっと検討中。

2007-03-23

[][]ユーザ定義型に対応したいなら、DatabaseMetaDataは、使えません。

昨日のエントリーに書いたようなストアドプロシージャを、

S2DaoのPROCEDUREアノテーションを使って呼び出すと、

どういう問題が発生するのか?


こんなDaoを書いてアクセスしてみる。

package examples.dao;

import java.util.Map;

public interface StoredTypeTestDao {
	public Class BEAN = Employee.class;

	public String getNewPerson_PROCEDURE = "NEW_PERSON";

	public Map getNewPerson(Integer id, String name);

	public String getPersonName_PROCEDURE = "PERSON_NAME";

	public Map getPersonName(Person person);

	public String getNewPersonArray_PROCEDURE = "NEW_PERSON_ARRAY";

	public Map getNewPersonArray(Integer id1, String name1, Integer id2,
			String name2);

	public String getPersonNameMerge_PROCEDURE = "PERSON_NAME_MERGE";

	public Map getPersonNameMerge(Person[] personArray);
}

クライアント側のソースは省略)


このDaoのいずれかのメソッドを呼び出すと、

org.seasar.dao.handler.AbstractBasicProcedureHandler#bindArgsメソッド内で

SQLRuntimeExceptionが発生する。


問題発生箇所は、値をcall文に設定している所。

for (int i = 0; i < columnTypes.length; i++) {
    if (isOutputColum(columnInOutTypes[i].intValue())) {
        ps.registerOutParameter(i + 1, columnTypes[i].intValue());
    }
    if (isInputColum(columnInOutTypes[i].intValue())) {
        ps.setObject(i + 1, args[argPos++], columnTypes[i].intValue());
    }
}

ここで、ユーザ定義型や配列を設定する際に、

columnTypes[i].intValue()の値として「1111」が渡されるから、

型が合わず、SQLRuntimeExceptionが発生しているらしい。


ちなみに1111とは、「java.sql.Types.OTHER」の値で、

要は「カラムの型がよく分からない」ってこと。


ところで、

S2Daoは、Daoの初回アクセス時にDBMSからメタデータを取得し、

それを使って型変換を行なったり、引数の数を決めて、SQL文を作成している。


そう、

OracleJDBCドライバを使っていると、

DatabaseMetaData#getProcedureColumnsで

ストアドプロシージャの戻り値引数を確認しようとすると、

ユーザ定義型や配列は、すべて「1111 (java.sql.Types.OTHER)」になっちゃう、ってこと。


なので、DatabaseMetaDataを利用するだけでは、

ストアドプロシージャの引数戻り値に使われている型の詳細が分からない。

2007/03/24追記。

DatabaseMetaData#getProcedureColumnsには型名も入っているし、

DatabaseMetaData#getUDTsを使えば、型の詳細はもうちょっと分かります

じゃぁどうするか。


うん、自前でSQLを発行して、型の情報を取ってしまおう。

[]ユーザ定義型に対応したいなら、自前でディクショナリから定義を取ろう。

ということで、自前でOracleのディクショナリから値を取るSQL文。

SELECT
  *
FROM
  all_arguments
WHERE
  object_name LIKE 'NEW_PERSON'
ORDER BY
  owner, package_name, overload, sequence
;

この戻り値を、Javaロジックで整理してやれば、必要な定義は全部取れる。


Oracleへの完全依存になっちゃったけど、これ以外に方法はなさそうなのでー。

2007-03-22

[][]s2dao-oracle作成開始。

s2dao-oracleを作成するにあたって、まずは目的を明確にしよう。

こんな所。


できるだけ、OracleJDBCドライバに依存しない形、、、なんかは、考えない。

どうせ、oracle.sql.StructDescriptorとかoracle.sql.ArrayDescriptorを使う事になるので。

[]戻り値がStructのストアドプロシージャ

まずはS2Dao云々は置いといて、戻り値がStructのストアドプロシージャの扱い方を考える。

Oracleオブジェクト型の操作を参考に、以下のような定義を作成する。

CREATE TYPE PERSON AS object
(
  ID NUMBER(5),
  NAME VARCHAR2(30)
)
/ 

CREATE OR REPLACE FUNCTION "NEW_PERSON"
(
  ID in NUMBER,
  NAME in VARCHAR2
)
return PERSON
is
begin
  return PERSON(ID, NAME);
end;
/

これに対応したJavaのソースは、以下の通り。

package examples.dao;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Struct;
import java.sql.Types;

import org.seasar.extension.dbcp.ConnectionPool;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class StoredTypeReturnClient {
	private static final String PATH = "j2ee.dicon";

	public static void main(String[] args) throws Exception {
		S2Container container = S2ContainerFactory.create(PATH);
		container.init();
		ConnectionPool pool = (ConnectionPool) container
				.getComponent(ConnectionPool.class);
		Connection conn = pool.checkOut();
		CallableStatement stmt = conn
				.prepareCall("{? = call NEW_PERSON(?, ?)}");

		stmt.registerOutParameter(1, Types.STRUCT, "PERSON");
		stmt.setInt(2, 1000);
		stmt.setString(3, "USER1");

		stmt.execute();

		Object o = stmt.getObject(1);

		Struct struct = (Struct) o;
		Object[] attrs = struct.getAttributes();

		System.out.println(attrs[0]);
		System.out.println(attrs[1]);
	}
}

これで、戻り値が正しく取れました。

これだけなら、Oracleドライバへの依存は不要で、

後は、Struct → Dtoマッピングを行なえば良いだけ。


これぐらいなら、簡単なんだけどなー。

[]引数がStructのストアドプロシージャ

続いて、引数がStructのストアドプロシージャの扱い方を考える。

PERSON型を渡して、PERSON.NAMEを返すようなプロシージャを定義する。


CREATE OR REPLACE FUNCTION "PERSON_NAME"
(
  USER in PERSON
)
return VARCHAR2
is
begin
  return USER.NAME;
end;
/

これに対応したJavaのソースは、以下の通り。

package examples.dao;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Struct;
import java.sql.Types;

import org.seasar.extension.dbcp.ConnectionPool;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class StoredTypeArgClient {
	private static final String PATH = "j2ee.dicon";

	public static void main(String[] args) throws Exception {
		S2Container container = S2ContainerFactory.create(PATH);
		container.init();
		ConnectionPool pool = (ConnectionPool) container
				.getComponent(ConnectionPool.class);
		Connection conn = pool.checkOut();

		Person person = new Person();
		person.setId(Integer.valueOf(100));
		person.setName("USER1");

		CallableStatement stmt = conn.prepareCall("{? = call PERSON_NAME(?)}");
		stmt.registerOutParameter(1, Types.VARCHAR);
		stmt.setObject(2, person, Types.STRUCT);

		stmt.execute();

		Object o = stmt.getObject(1);
		System.out.println(o);
	}
}

さて、ここでPerson型をどう扱うべきか。

選択肢としては、

  • ORADataの実装 (toDatumメソッドを実装する)
  • SQLDataの実装 (getSQLTypeName, readSQL, writeSQLを実装する)

の2種類があるんだけど、ここでは簡単なORADataを選択する。


真面目にSQLDataを実装すれば、

OracleJDBCドライバに依存しなくて済むかも知れないんだけど

どうせ、Arrayを扱う時に、Oracleドライバに依存する事になるので。

package examples.dao;

import java.sql.Connection;
import java.sql.SQLException;

import oracle.sql.Datum;
import oracle.sql.ORAData;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;

public class Person implements ORAData {
	Integer id;

	String name;

	public Datum toDatum(Connection conn) throws SQLException {
		StructDescriptor sd = new StructDescriptor("PERSON", conn);
		STRUCT struct = new STRUCT(sd, conn,
				new Object[] { this.id, this.name });
		return struct;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

はい、これで、引数が正常に渡せて、戻り値も正しく取れました。

[]戻り値がArrayのストアドプロシージャ

次は、戻り値がArrayのストアドプロシージャ。

やはり公式ドキュメントのOracleコレクションの操作が参考になります。


定義したプロシージャの詳細は省略、気になる人はソースを読んで!

CREATE TYPE PERSON_ARRAY AS VARRAY(10) OF PERSON
/

CREATE OR REPLACE FUNCTION "NEW_PERSON_ARRAY"
(
  ID1 in NUMBER,
  NAME1 in VARCHAR2,
  ID2 in NUMBER,
  NAME2 in VARCHAR2
)
return PERSON_ARRAY
is
begin
  return PERSON_ARRAY(PERSON(ID1, NAME1), PERSON(ID2, NAME2));
end;
/

そして、対応したJavaのソースは、以下の通り。

package examples.dao;

import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Struct;
import java.sql.Types;

import org.seasar.extension.dbcp.ConnectionPool;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class StoredArrayReturnClient {
	private static final String PATH = "j2ee.dicon";

	public static void main(String[] args) throws Exception {
		S2Container container = S2ContainerFactory.create(PATH);
		container.init();
		ConnectionPool pool = (ConnectionPool) container
				.getComponent(ConnectionPool.class);
		Connection conn = pool.checkOut();

		CallableStatement stmt = conn
				.prepareCall("{? = call NEW_PERSON_ARRAY(?, ?, ?, ?)}");
		stmt.registerOutParameter(1, Types.ARRAY, "PERSON_ARRAY");
		stmt.setInt(2, 1000);
		stmt.setString(3, "USER1");
		stmt.setInt(4, 2000);
		stmt.setString(5, "USER2");

		stmt.execute();

		Array array = stmt.getArray(1);
		Object[] structs = (Object[]) array.getArray();

		System.out.println(((Struct) structs[0]).getAttributes()[0]);
		System.out.println(((Struct) structs[0]).getAttributes()[1]);
		System.out.println(((Struct) structs[1]).getAttributes()[0]);
		System.out.println(((Struct) structs[1]).getAttributes()[1]);
	}
}

なんかちょっとソースが見苦しくなってますが、

getArrayした結果に、もう一度getArrayすると、

中身がStructなObject配列が取得できます。


これまた、戻り値を扱うだけなら、

OracleJDBCドライバには依存せずに作れます。


[]引数がArrayのストアドプロシージャ

本日最後、引数がArrayのストアドプロシージャ。

これがちょっと面倒くさい。


まずはストアドプロシージャ定義から。

配列で渡されたPersonの名前を結合するだけ。

CREATE OR REPLACE FUNCTION "PERSON_NAME_MERGE"
(
  USERS in PERSON_ARRAY
)
return VARCHAR2
is
begin
  return USERS(1).NAME || USERS(2).NAME;
end;
/

それを扱うJavaソース。

package examples.dao;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.Types;

import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;

import org.seasar.extension.dbcp.ConnectionPool;
import org.seasar.extension.dbcp.ConnectionWrapper;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class StoredArrayArgClient {
	private static final String PATH = "j2ee.dicon";

	public static void main(String[] args) throws Exception {
		S2Container container = S2ContainerFactory.create(PATH);
		container.init();
		ConnectionPool pool = (ConnectionPool) container
				.getComponent(ConnectionPool.class);
		Connection conn = pool.checkOut();

		Person person1 = new Person();
		person1.setId(Integer.valueOf(100));
		person1.setName("USER1");
		Person person2 = new Person();
		person2.setId(Integer.valueOf(200));
		person2.setName("USER1");

		conn = ((ConnectionWrapper) conn).getPhysicalConnection();
		ArrayDescriptor ad = new ArrayDescriptor("PERSON_ARRAY", conn);
		ARRAY array = new ARRAY(ad, conn, new Object[] { person1, person2 });

		CallableStatement stmt = conn
				.prepareCall("{? = call PERSON_NAME_MERGE(?)}");
		stmt.registerOutParameter(1, Types.VARCHAR);
		stmt.setObject(2, array, Types.ARRAY);

		stmt.execute();

		Object o = stmt.getObject(1);
		System.out.println(o);
	}
}

何が面倒くさいって、

まずはConnectionWrapperのままArrayDescriptorをnewすると

ClassCastExceptionが発生しちゃうところ。

仕方なく物理コネクションを取得して、渡しています。


もう一つ面倒くさいのは、ArrayDescriptorがここに現れてしまう所。

Structは、Javaのクラスと、ちょうど1対1の関係になるから、

Javaのクラス(Person.class)にStructDescriptorを配置できたんだけど、、、

Arrayは、Java配列をクラス化したような存在なので、

どこにArrayDescriptorを置けば良いのか、ちょっと決めづらい。


まぁ、ぼちぼち決めていこう。