いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

iBatis奮闘記-0007 (ユーティリティクラスの作成)

iBatisを使用していると、SqlMapClientを生成している部分などを
毎回記述すると面倒になります。iBatisのドキュメントにはSqlMapClientを生成するUtilクラスを
作成する例が記述されています。
てことで、面倒な初期化処理などを引き受けてくれるユーティリティクラスを作成しておきます。

[実行するという動作を定義しているインターフェース]

// vim:set ts=4 sw=4 et ws is nowrap ft=java:
package gsf.interfaces;

/**
 * 何かを実行するものを示すインターフェースです.<br/>
 * このインターフェースはマーカーインターフェースとなっています.<br/>
 * 実際の詳細インターフェースは本インターフェースを継承したインターフェースで<br/>
 * 行ってください.<br/>
 *
 * @author gsf_zero1
 *
 */
public interface Executor{
    //
    // no contents
    //
}

[SqlMapClient実行用インターフェース]

// vim:set ts=4 sw=4 et ws is nowrap ft=java:
package gsf.interfaces.sqlmap;

import java.sql.*;

import gsf.interfaces.*;

import com.ibatis.sqlmap.client.*;

/**
 * iBatis SqlMapClientの実行のシンタックスを定義しているインターフェースです.<br/>
 * どのようなSqlMapの実行処理を行うかは、各実装クラスにて実装されます.<br/>
 *
 * @author gsf_zero1
 *
 * @see gsf.interfaces.Executor
 *
 */
public interface SqlMapClientExecutor extends Executor{

    /**
     * 実行する.<br/>
     * 
     * @param  sqlMap SqlMapClientオブジェクト
     *
     * @return 実行結果
     *
     * @throws SQLException SQL実行中にエラーが発生した場合
     *
     */
    Object execute(SqlMapClient sqlMap) throws SQLException;

}

[各例外クラス(実行時例外)]

// vim:set ts=4 sw=4 et ws is nowrap ft=java:
package gsf.exception;

/**
 * SqlMap関連でエラーが発生した場合に発生する実行時例外クラス.<br/>
 *
 * @author gsf_zero1
 *
 */
public class SqlMapExecuteFailureRuntimeException extends RuntimeException{

    /**
     * コンストラクタ.<br/>
     *
     */
    public SqlMapExecuteFailureRuntimeException(){
        super();
    }

    /**
     * コンストラクタ.<br/>
     *
     * @param message メッセージ
     *
     */
    public SqlMapExecuteFailureRuntimeException(String message){
        super(message);
    }

    /**
     * コンストラクタ.<br/>
     *
     * @param cause 元原因例外オブジェクト
     *
     */
    public SqlMapExecuteFailureRuntimeException(Throwable cause){
        super(cause);
    }

    /**
     * コンストラクタ.<br/>
     *
     * @param message メッセージ
     * @param cause   元原因例外オブジェクト
     *
     */
    public SqlMapExecuteFailureRuntimeException(String message, Throwable cause){
        super(message, cause);
    }
}

// vim:set ts=4 sw=4 et ws is nowrap ft=java:
package gsf.exception;

/**
 * SqlMapの生成に失敗した場合に発生する実行時例外クラス.<br/>
 *
 * @author gsf_zero1
 *
 */
public class SqlMapInitializeFailureRuntimeException extends RuntimeException{
    
    /**
     * コンストラクタ.<br/>
     *
     */
    public SqlMapInitializeFailureRuntimeException(){
        super();
    }

    /**
     * コンストラクタ.<br/>
     *
     * @param message メッセージ
     *
     */
    public SqlMapInitializeFailureRuntimeException(String message){
        super(message);
    }

    /**
     * コンストラクタ.<br/>
     *
     * @param cause 元原因例外オブジェクト
     *
     */
    public SqlMapInitializeFailureRuntimeException(Throwable cause){
        super(cause);
    }
    
    /**
     * コンストラクタ.<br/>
     *
     * @param message メッセージ
     * @param cause   元原因例外オブジェクト
     *
     */
    public SqlMapInitializeFailureRuntimeException(String message, Throwable cause){
        super(message, cause);
    }

}

[ユーティリティクラス用Enumクラス]

// vim:set ts=4 sw=4 et ws is nowrap ft=java:
package gsf.utils.sqlmap;

/**
 * iBatis SqlMapの実行モードを定義している列挙クラス.<br/>
 *
 * @author gsf_zero1
 *
 */
public enum SqlMapExecuteMode{

    /** トランザクション無し */
    NO_TRANSACTION,

    /** トランザクション有り */
    WITH_TRANSACTION,

    /** バッチ処理 */
    BATCH_EXECUTE;

}

[ユーティリティクラス]

// vim:set ts=4 sw=4 et ws is nowrap ft=java:
package gsf.utils.sqlmap;

import java.lang.reflect.*;
import java.io.*;
import java.sql.*;
import java.util.*;

import gsf.interfaces.sqlmap.*;
import gsf.exception.*;
import static gsf.utils.sqlmap.SqlMapExecuteMode.NO_TRANSACTION;
import static gsf.utils.sqlmap.SqlMapExecuteMode.WITH_TRANSACTION;
import static gsf.utils.sqlmap.SqlMapExecuteMode.BATCH_EXECUTE;

import com.ibatis.common.resources.*;
import com.ibatis.sqlmap.client.*;

/**
 * iBatis用のユーティリティクラス.<br/>
 * SqlMapClientオブジェクト取得のショートカットメソッド等が<br/>
 * 存在します.<br/>
 *
 * @author gsf_zero1
 *
 */
public class SqlMapUtils{

    /** SqlMapClientのプール */
    protected static Map<String, SqlMapClient> POOL_SQLMAP = new HashMap<String, SqlMapClient>();

    /** デフォルトのSQLMAP設定ファイル */
    protected static final String SQLMAP_CONFIG_DEFAULT = "SqlMapConfig.xml";

    /**
     * SqlMapClientオブジェクトをプールから取得します.<br/>
     * プールに存在しない場合、新規に作成しそれをプーリングしてから返します.<br/>
     *
     * @param resource SqlMap設定ファイルへのリソースパス
     *
     * @return SqlMapClientオブジェクト
     *
     * @throws SqlMapInitializeFailureRuntimeException SqlMapClientの生成に失敗した場合
     *
     */
    protected static SqlMapClient getSqlMapClientFromPool(String resource){

        SqlMapClient sqlMap = POOL_SQLMAP.get(resource);

        if(sqlMap == null){

            try{

                sqlMap = SqlMapClientBuilder.buildSqlMapClient(Resources.getResourceAsReader(resource));

            }catch(IOException ex){
                throw new SqlMapInitializeFailureRuntimeException(ex);
            }

            POOL_SQLMAP.put(resource, sqlMap);

        }

        return sqlMap;

    }

    /**
     * デフォルトの設定ファイル(SqlMapConfig.xml)を使用して<br/>
     * SqlMapClientオブジェクトを取得します.<br/>
     * 
     * @return SqlMapClientオブジェクト
     *
     * @throws SqlMapInitializeFailureRuntimeException SqlMapClientの生成に失敗した場合
     *
     */
    public static SqlMapClient getSqlMapClient(){

        return getSqlMapClientFromPool(SQLMAP_CONFIG_DEFAULT);

    }

    /**
     * 指定された設定ファイルを使用して<br/>
     * SqlMapClientオブジェクトを取得します.<br/>
     * 
     * @return SqlMapClientオブジェクト
     *
     * @throws SqlMapInitializeFailureRuntimeException SqlMapClientの生成に失敗した場合
     *
     */
    public static SqlMapClient getSqlMapClient(String resource){

        return getSqlMapClientFromPool(resource);

    }

    /**
     * 同クラスのgetSqlMapClient()を内部で呼び出し、通常(トランザクション無し)モードでexecute()メソッドを<br/>
     * 呼び出します。内部的な動作は、execute(SqlMapClient, Object, String, SqlMapExecuteMode)<br/>
     * を参照してください.<br/>
     *
     * @param receiver   レシーバーオブジェクト
     * @param methodName メソッド名
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.utils.sqlmap.SqlMapUtils#execute(com.ibatis.sqlmap.client.SqlMapClient, Object, String, gsf.utils.sqlmap.SqlMapExecuteMode)
     *
     */
    public static Object executeNoTransaction(Object receiver, String methodName){

        return execute(getSqlMapClient(), receiver, methodName, NO_TRANSACTION);

    }

    /**
     * 引数としてSqlMapClientExecutorインターフェースの実装を渡すexecuteNoTransactionメソッドのショートカットです.<br/>
     * 同クラスのgetSqlMapClient()を内部で呼び出し、通常(トランザクション無し)モードでexecute()メソッドを<br/>
     * 呼び出します。内部的な動作は、execute(SqlMapClient, Object, String, SqlMapExecuteMode)<br/>
     * を参照してください.<br/>
     *
     * @param executor SqlMapClientExecutorインターフェースの実装
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.interfaces.sqlmap.SqlMapClientExecutor
     * @see gsf.utils.sqlmap.SqlMapUtils#execute(com.ibatis.sqlmap.client.SqlMapClient, Object, String, gsf.utils.sqlmap.SqlMapExecuteMode)
     *
     */
    public static Object executeNoTransaction(SqlMapClientExecutor executor){

        return executeNoTransaction(executor, "execute");

    }

    /**
     * 同クラスのgetSqlMapClient()を内部で呼び出し、トランザクションモードでexecute()メソッドを<br/>
     * 呼び出します。内部的な動作は、execute(SqlMapClient, Object, String, SqlMapExecuteMode)<br/>
     * を参照してください.<br/>
     *
     * @param receiver   レシーバーオブジェクト
     * @param methodName メソッド名
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.utils.sqlmap.SqlMapUtils#execute(com.ibatis.sqlmap.client.SqlMapClient, Object, String, gsf.utils.sqlmap.SqlMapExecuteMode)
     *
     */
    public static Object executeWithTransaction(Object receiver, String methodName){

        return execute(getSqlMapClient(), receiver, methodName, WITH_TRANSACTION);

    }

    /**
     * 引数としてSqlMapClientExecutorインターフェースの実装を渡すexecuteWithTransactionメソッドのショートカットです.<br/>
     * 同クラスのgetSqlMapClient()を内部で呼び出し、トランザクションモードでexecute()メソッドを<br/>
     * 呼び出します。内部的な動作は、execute(SqlMapClient, Object, String, SqlMapExecuteMode)<br/>
     * を参照してください.<br/>
     *
     * @param executor SqlMapClientExecutorインターフェースの実装
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.interfaces.sqlmap.SqlMapClientExecutor
     * @see gsf.utils.sqlmap.SqlMapUtils#execute(com.ibatis.sqlmap.client.SqlMapClient, Object, String, gsf.utils.sqlmap.SqlMapExecuteMode)
     *
     */
    public static Object executeWithTransaction(SqlMapClientExecutor executor){

        return executeWithTransaction(executor, "execute");

    }

    /**
     * 同クラスのgetSqlMapClient()を内部で呼び出し、バッチ処理実行モードでexecute()メソッドを<br/>
     * 呼び出します。内部的な動作は、execute(SqlMapClient, Object, String, SqlMapExecuteMode)<br/>
     * を参照してください.<br/>
     *
     * @param receiver   レシーバーオブジェクト
     * @param methodName メソッド名
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.utils.sqlmap.SqlMapUtils#execute(com.ibatis.sqlmap.client.SqlMapClient, Object, String, gsf.utils.sqlmap.SqlMapExecuteMode)
     *
     */
    public static Object executeAsBatch(Object receiver, String methodName){

        return execute(getSqlMapClient(), receiver, methodName, BATCH_EXECUTE);

    }

    /**
     * 引数としてSqlMapClientExecutorインターフェースの実装を渡すexecuteAsBatchメソッドのショートカットです.<br/>
     * 同クラスのgetSqlMapClient()を内部で呼び出し、バッチ処理実行モードでexecute()メソッドを<br/>
     * 呼び出します。内部的な動作は、execute(SqlMapClient, Object, String, SqlMapExecuteMode)<br/>
     * を参照してください.<br/>
     *
     * @param executor SqlMapClientExecutorインターフェースの実装
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.interfaces.sqlmap.SqlMapClientExecutor
     * @see gsf.utils.sqlmap.SqlMapUtils#execute(com.ibatis.sqlmap.client.SqlMapClient, Object, String, gsf.utils.sqlmap.SqlMapExecuteMode)
     *
     */
    public static Object executeAsBatch(SqlMapClientExecutor executor){

        return executeAsBatch(executor, "execute");

    }

    /**
     * 指定されたレシーバーオブジェクトのメソッドを指定されたモードで実行します.<br/>
     * メソッドの実行中にエラーが発生した場合、SqlMapExecuteFailureRuntimeException(実行時例外)がスローされます.<br/>
     * また、実行されるメソッドは、以下の規則に従っている必要があります.<br/>
     * <br/>
     * <b>
     *      <ol>
     *          <li>メソッド名の命名規則は自由.</li>
     *          <li>メソッドの引数は、SqlMapClient一つである必要がある.</li>
     *          <li>メソッドの返り値の型は、自由.</li>
     *          <li>メソッドのスコープは(public, protected, private, デフォルト)の全て自由.</li>
     *      </ol>
     * </b>
     * <br/>
     * また、メソッドの戻り値はそのままObject型として返却されます。<br/>
     * キャストして特定の型に変換してください.<br/>
     * <br/>
     * このメソッドは、トランザクションモードが指定されている場合、メソッドの実行が成功した場合は、<br/>
     * コミットを、失敗した場合は、ロールバックを行います。<br/>
     *
     * @param sqlMap      SqlMapClientオブジェクト
     * @param receiver    レシーバーオブジェクト
     * @param methodName  実行メソッド名
     * @param mode        実行モード
     *
     * @return メソッド実行後の戻り値.
     *
     * @throws SqlMapExecuteFailureRuntimeException メソッド実行中にエラーが発生した場合.
     *
     * @see gsf.utils.sqlmap.SqlMapExecuteMode
     *
     */
    public static Object execute(SqlMapClient sqlMap, Object receiver, String methodName, SqlMapExecuteMode mode){

        Object result = null;
        
        try{

            Method targetMethod = 
                        receiver.getClass().getDeclaredMethod(methodName, new Class{SqlMapClient.class});

            targetMethod.setAccessible(true);

            if(mode == NO_TRANSACTION){

                result = targetMethod.invoke(receiver, new Object{sqlMap});

            }else if(mode == WITH_TRANSACTION){

                try{

                    sqlMap.startTransaction();

                    result = targetMethod.invoke(receiver, new Object{sqlMap});

                    sqlMap.commitTransaction();

                }finally{
                    sqlMap.endTransaction();
                }

            }else if(mode == BATCH_EXECUTE){

                try{

                    sqlMap.startTransaction();

                    sqlMap.startBatch();

                    result = targetMethod.invoke(receiver, new Object{sqlMap});

                    sqlMap.executeBatch();

                    sqlMap.commitTransaction();

                }finally{
                    sqlMap.endTransaction();
                }

            }

        }catch(InvocationTargetException ex){
            throw new SqlMapExecuteFailureRuntimeException("メソッド実行中にエラー.", ex);
        }catch(IllegalAccessException ex){
            throw new SqlMapExecuteFailureRuntimeException("メソッドへのアクセスが不正です.", ex);
        }catch(NoSuchMethodException ex){
            throw new SqlMapExecuteFailureRuntimeException("該当する実行メソッドが見つかりません.", ex);
        }catch(SQLException ex){
            throw new SqlMapExecuteFailureRuntimeException(ex);
        }

        return result;
    }

}

今回は、よくあるコネクション取得系のgetSqlMapClient以外に
SqlMapClientを使用してデータの取得や更新作業を行うためのショートカットメソッドを
定義しています。

使い方は、適当にクラスを定義して、その中にSqlMapClientを引数にとるメソッドを定義します。
後は、そのオブジェクトとメソッド名を渡して、結果をもらいます。

次のページング機能リストのサンプルにて使用してますので、そちらを参照願います。
実行できる規約の部分を修正すればもっと幅の広い用件に答えられるようになるかもしれません。
また、iBatisのDAOフレームワークを使用すればもっと楽になったりします。
DAOフレームワークについては、ページング機能が終わった後あたりから、記述しようかなって感じです。