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]にあります。