春のご案内AbstractRoutingDatasource

1概要

このクイック記事では、現在のコンテキストに基づいて実際の DataSource を動的に決定する** 方法として、Springの AbstractRoutingDatasource を見ていきます。

その結果、 DataSource 検索ロジックをデータアクセスコードから除外できることがわかります。

2 Mavenの依存関係

pom.xml で依存関係として spring-context、spring-jdbc、spring-test、 、および h2 を宣言することから始めましょう。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.8.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.8.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.195</version>
        <scope>test</scope>
    </dependency>
</dependencies>

依存関係の最新バージョンはhttps://search.maven.org/classic/#search%7Cga%7C1%7C(g%3A%22org.springframework%22%20AND%20(a%3A%22spring-)にあります。コンテキスト%22%20OR%20a%3A%22spring-jdbc%22%20OR%20a%3A%22spring-test%22))%20OR%20(g%3A%22com.h2データベース%22%20AND%20a%3A% 22h2%22)[ここ]

3データソースコンテキスト

AbstractRoutingDatasource はどの実際の DataSource にルーティングするかを知るための情報を必要とします。この情報は通常__Contextと呼ばれます。

AbstractRoutingDatasource で使用される Context は任意の Objectでも構いませんが、 an enum はそれらを定義するために使用されます。この例では、次の実装でコンテキストとして ClientDatabase の概念を使用します。

public enum ClientDatabase {
    CLIENT__A, CLIENT__B
}

注目に値するのは、実際には、コンテキストは問題のドメインにとって意味があるものなら何でもということです。

例えば、もう1つの一般的なユースケースはコンテキストを定義するために Environment の概念を使用することを含みます。そのようなシナリオでは、コンテキストは PRODUCTION DEVELOPMENT 、および TESTING を含む列挙型になります。

4コンテキストホルダー

コンテキストホルダーの実装は現在のコンテキストを ThreadLocal 参照として格納するコンテナーです。

参照を保持することに加えて、参照を設定、取得、および消去するための静的メソッドを含める必要があります。 AbstractRoutingDatasource はContextHolderにContextを問い合わせ、それから実際の DataSource を検索するためにcontextを使います。

コンテキストが現在実行中のスレッドにバインドされるように、ここで ThreadLocal を使用することは非常に重要です。

データアクセスロジックが複数のデータソースにまたがってトランザクションを使用する場合の動作が信頼できるものになるように、このアプローチをとることが不可欠です。

public class ClientDatabaseContextHolder {

    private static ThreadLocal<ClientDatabase> CONTEXT
      = new ThreadLocal<>();

    public static void set(ClientDatabase clientDatabase) {
        Assert.notNull(clientDatabase, "clientDatabase cannot be null");
        CONTEXT.set(clientDatabase);
    }

    public static ClientDatabase getClientDatabase() {
        return CONTEXT.get();
    }

    public static void clear() {
        CONTEXT.remove();
    }
}

5データソースルーター

Springを抽象化するために ClientDataSourceRouter を定義します。 ClientDatabaseContextHolder を照会して適切なキーを返すために必要な determineCurrentLookupKey メソッドを実装します。

AbstractRoutingDataSource 実装は残りの作業を処理し、適切な__DataSourceを透過的に返します。

public class ClientDataSourceRouter
  extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return ClientDatabaseContextHolder.getClientDatabase();
    }
}

6. 構成

AbstractRoutingDataSource を設定するには、 DataSource オブジェクトへのコンテキストの Map が必要です。コンテキストセットがない場合に使用するデフォルトの DataSource を指定することもできます。

使用する __DataSource __はどこからでも取得できますが、通常は実行時に作成されるかJNDIを使用して検索されます。

@Configuration
public class RoutingTestConfiguration {

    @Bean
    public ClientService clientService() {
        return new ClientService(new ClientDao(clientDatasource()));
    }

    @Bean
    public DataSource clientDatasource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        DataSource clientADatasource = clientADatasource();
        DataSource clientBDatasource = clientBDatasource();
        targetDataSources.put(ClientDatabase.CLIENT__A,
          clientADatasource);
        targetDataSources.put(ClientDatabase.CLIENT__B,
          clientBDatasource);

        ClientDataSourceRouter clientRoutingDatasource
          = new ClientDataSourceRouter();
        clientRoutingDatasource.setTargetDataSources(targetDataSources);
        clientRoutingDatasource.setDefaultTargetDataSource(clientADatasource);
        return clientRoutingDatasource;
    }

   //...
}

7. 使用法

AbstractRoutingDataSource を使用するときは、最初にコンテキストを設定してから操作を実行します。パラメータとしてコンテキストを受け取り、呼び出し後にデータアクセスコードに委任してコンテキストをクリアする前に設定するサービスレイヤを使用します。

サービスメソッド内でコンテキストを手動で消去する代わりに、消去ロジックをAOPポイントカットで処理できます。

特にデータアクセスロジックが複数のデータソースとトランザクションにまたがる場合、コンテキストは スレッド境界 であることを覚えておくことが重要です。

public class ClientService {

    private ClientDao clientDao;

   //standard constructors

    public String getClientName(ClientDatabase clientDb) {
        ClientDatabaseContextHolder.set(clientDb);
        String clientName = this.clientDao.getClientName();
        ClientDatabaseContextHolder.clear();
        return clientName;
    }
}

8結論

このチュートリアルでは、Spring AbstractRoutingDataSource の使い方の例を見ました。 Client という概念を使用してソリューションを実装しました。各クライアントには DataSource があります。

そしていつものように、例はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-jpa[over on GitHub]にあります。