SQLによる例外処理

個人的には例外はキャッチしない方針なのですが、SQL関連の例外についてはそうも行きません。重複やNULL制約などは例外ではなくエラー扱いにしたいところです。

try {
    int cnt = IUserDao.Insert(user);
}
catch (SQLRuntimeException ex) {
    // SQLRuntimeExceptionはSeasar.NETの型なので局所化したい
    // OracleExceptionはODP.NETの型なので局所化したい
    OracleException oex = ex.InnerException;
    Trace.WriteLine (oex.Message);
    Trace.WriteLine (oex.Number);
    return false;
}

たとえば、上記のような処理が入ります。例ではS2Dao.NETを使っているので、SQLRuntimeExceptionが発生します。更にOracleのエラーコードを取得しようとするとOracleExceptionにアクセスしなければなりません。もちろん、ラッパーを用意すればいいのですが、SQL毎にイチイチ書くのも面倒です。
折角、Seasar.NETを使うのだからAOPを利用してみます。

namespace My.Dao
{
    public class SQLExceptionInterceptor : IMethodInterceptor
    {
        public object Invoke(IMethodInvocation invocation)
        {
            object ret = null;
            
            try
            {
                ret = invocation.Proceed();
            }
            catch (SQLRuntimeException ex)
            {
                // SQLエラーの場合は、例外ではなく戻り値にする
                // 独自の例外クラスでOracleExceptionを隠蔽
                ret = new SQLException(ex.Message, ex.InnerException);                
            }
            return ret;
        }
    }    
}

インターセプタークラスを作成し、メソッド呼び出しに割り込んでSQL関連の例外を戻り値に変更しています。これをdiconファイルに登録します。

<component class="My.Dao.IUserDao">
  <aspect>SQLExceptionInterceptor</aspect>
  <aspect>Ex.DaoInterceptor</aspect>
</component>

登録順序はSQLExceptionInterceptor、DaoInterceptorの順。

こうすると、

object ret = IUserDao.Insert(user);
if (ret is SQLException) {
    // SQLに関する例外は回復可能
    SQLException sqlErr = ret as SQLException;

    // 以下、エラー処理
}

// 成功した場合は、挿入した行数が戻る
int cnt = (int)ret;

例外は飛ばないので、普通にエラー処理が行えます。

例外関連の扱いは苦手なので、もう少し良いやり方がありましたら教えてください。