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