S2JDBCで複数データソースを使い分ける

S2JDBCで複数データソースを使い分ける方法はこちらにあった。
今回自分がしたいのは、同じ構成のテーブルでデータソースを分けたいので、dataSourceFactoryを使って分けるようにする。
一応自分がした設定を記述してみる。

設定ファイル

jdbc_datasource1.diconとjdbc_datasource2.diconはこちらにあるような感じで書いた。
で、2つのファイルをインクルードして、SelectableDataSourceProxyとDataSourceFactoryImplを定義した。
s2jdbc.diconもjdbcManagerは1つにするので、変更していない。

jdbc_datasource1.dicon
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
    
    ....
    
<components namespace="jdbc">
    <component name="datasource1DataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>
jdbc_datasource2.dicon
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
    
    ....
    
<components namespace="jdbc">
    <component name="datasource2DataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>
jdbc.dicon
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components namespace="jdbc">
    <include path="jdbc_datasource1.dicon"/>
    <include path="jdbc_datasource2.dicon"/>
    
    <component name="dataSource" class="org.seasar.extension.datasource.impl.SelectableDataSourceProxy"/>
    <component name="dataSourceFactory" class="org.seasar.extension.datasource.impl.DataSourceFactoryImpl"/>
</components>

インターセプタ

データソース用のアノテーションenumとインターセプタを作った。
アノテーションで指定されているデータソースenumを見て、データソースを動的に切り替える仕組み。

インターセプタ
public class DataSourceSettingInterceptor extends AbstractInterceptor {
    /** データソースファクトリ */
    @Resource
    protected DataSourceFactory dataSourceFactory;

    /** デフォルトデータソース */
    private static final DataSourceType DEFAULT_DATA_SOURCE = DataSourceType.DATASOURCE1;

    /**
     * {@inheritDoc}
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        DataSourceType currentDataSourceType = findCurrentDataSourceType();
        try {
            DataSourceType dataSourceType = findDataSourceType(invocation.getMethod());
            dataSourceFactory.setSelectableDataSourceName(dataSourceType.getDataSourceName());

            return invocation.proceed();
        } finally {
            dataSourceFactory.setSelectableDataSourceName(currentDataSourceType.getDataSourceName());
        }
    }

    /**
     * 現在のデータソースタイプを取得する。<br>
     * @return 現在のデータソースタイプ
     */
    private DataSourceType findCurrentDataSourceType() {
        String currentDataSourceName = dataSourceFactory.getSelectableDataSourceName();

        if (StringUtils.isEmpty(currentDataSourceName)) {
            return DEFAULT_DATA_SOURCE;
        }

        DataSourceType dataSourceType;
        try {
            dataSourceType = DataSourceType.toDataSourceType(currentDataSourceName);
        } catch (IllegalArgumentException e) {
            // 想定外のデータソース名が渡された場合は、デフォルトデータソースを返す。
            return DEFAULT_DATA_SOURCE;
        }

        return dataSourceType;
    }

    /**
     * データソースタイプを取得する。<br>
     * @param method メソッド
     * @return データソースタイプ
     */
    private DataSourceType findDataSourceType(Method method) {
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource == null) {
            return DEFAULT_DATA_SOURCE;
        }

        return dataSource.type();
    }
}
アノテーション
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    /**
     * データソースタイプを設定する。<br>
     */
    DataSourceType type();

}
enum
public enum DataSourceType {
    /** データソース1用 */
    DATASOURCE1("datasource1"),

    /** データソース2用 */
    DATASOURCE2("datasource2");

    /** データソース名 */
    private String dataSourceName;

    /**
     * コンストラクタ<br>
     * @param newDataSourceName データソース名
     */
    private DataSourceType(String newDataSourceName) {
        this.dataSourceName = newDataSourceName;
    }

    /**
     * データソース名を取得する。<br>
     * @return データソース名
     */
    public String getDataSourceName() {
        return this.dataSourceName;
    }

    /**
     * 指定のデータソース名から対応するデータソースタイプを取得する。<br>
     * @param dataSourceName データソース名
     * @return 対応するデータソースタイプ
     */
    public static DataSourceType toDataSourceType(String dataSourceName) {
        for (DataSourceType dataSourceType : DataSourceType.values()) {
            if (dataSourceType.getDataSourceName().equals(dataSourceName)) {
                return dataSourceType;
            }
        }
        throw new IllegalArgumentException("指定のデータソース名に合致するデータソースタイプはありません。");
    }
}

インターセプタの使い方

一応、使い方。

@DataSource(type = DataSourceType.DATASOURCE1)
public String hoge() {
    // do something
}



あと、一部の機能のみデータソースを切り替えて実行したいときのために、エグゼキュータを作ってみた。

エグゼキュータ

public final class SelectableDataSourceExecutor {
    /**
     * コンストラクタ<br>
     */
    private SelectableDataSourceExecutor() {
    }

    /**
     * データソースを指定のデータソースタイプに切り替えて実行する。
     * @param <RESULT> 戻り値の型
     * @param dataSourceType データソースタイプ
     * @param handler ハンドラ
     * @return 結果
     */
    public static <RESULT> RESULT execute(DataSourceType dataSourceType, Handler<RESULT> handler) {
        DataSourceFactory dataSourceFactory = SingletonS2Container.getComponent(DataSourceFactory.class);

        String currentDataSourceName = dataSourceFactory.getSelectableDataSourceName();
        try {
            dataSourceFactory.setSelectableDataSourceName(dataSourceType.getDataSourceName());
            return handler.handle();
        } finally {
            dataSourceFactory.setSelectableDataSourceName(currentDataSourceName);
        }
    }

    /**
     * データソースの切り替え時に呼び出されるハンドラ。<br>
     * @param <RESULT> 結果の型
     */
    public interface Handler<RESULT> {
        /**
         * データソースの切り替え後に実行する。<br>
         * @return 結果
         */
        RESULT handle();
    }
}

エグゼキュータの使い方

一応、使い方。

public void hoge(final HogeDao dao, final HogeEntity entity)
    // do something
    
    SelectableDataSourceExecutor.execute(DataSourceType.DATASOURCE1, new SelectableDataSourceExecutor.Handler<Integer>() {
        /**
         * {@inheritDoc}
         */
        @Override
        public Integer handle() {
            return dao.insert(entity);
        }
    });
    
    // do something
}