谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

引数の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が入った
ストアドプロシージャ呼び出しができるようになりました。