1. 概要

この簡単な記事では、Springの AbstractRoutingDatasource を、現在のコンテキストに基づいて実際のデータソースを動的に決定する方法として見ていきます。

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

2. Mavenの依存関係

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

<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>

依存関係の最新バージョンはここにあります。

Spring Bootを使用している場合は、Spring DataandTestのスターターを使用できます。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.195</version>
        <scope>test</scope>
    </dependency>
</dependencies>

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

AbstractRoutingDatasource には、ルーティングする実際のDataSourceを知るための情報が必要です。 この情報は通常、 環境。

AbstractRoutingDatasourceで使用されるContextは任意のオブジェクトにすることができますが、および列挙型はそれらを定義するために使用されます。 この例では、 ClientDatabase の概念をコンテキストとして使用し、次の実装を行います。

public enum ClientDatabase {
    CLIENT_A, CLIENT_B
}

実際には、コンテキストは問題のドメインにとって意味のあるものであれば何でもかまいません。

たとえば、別の一般的なユースケースでは、Environmentの概念を使用してコンテキストを定義します。 このようなシナリオでは、コンテキストは PRODUCTION DEVELOPMENT 、およびTESTINGを含む列挙型である可能性があります。

4. コンテキストホルダー

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

参照を保持することに加えて、参照を設定、取得、およびクリアするための静的メソッドが含まれている必要があります。 AbstractRoutingDatasource は、ContextHolderにコンテキストを照会し、コンテキストを使用して実際のDataSourceを検索します。

ここで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. データソースルーター

ClientDataSourceRouter を定義して、Spring AbstractRoutingDataSourceを拡張します。 必要なdetermineCurrentLookupKeyメソッドを実装して、 ClientDatabaseContextHolder を照会し、適切なキーを返します。

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;
    }

    // ...
}

Spring Bootを使用する場合、 application.properties ファイルでデータソースを構成できます(つまり、  ClientA ClientB ):

#database details for CLIENT_A
client-a.datasource.name=CLIENT_A
client-a.datasource.script=SOME_SCRIPT.sql

#database details for CLIENT_B
client-b.datasource.name=CLIENT_B
client-b.datasource.script=SOME_SCRIPT.sql

次に、DataSourcesのプロパティを保持するPOJOを作成できます。

@Component
@ConfigurationProperties(prefix = "client-a.datasource")
public class ClientADetails {

    private String name;
    private String script;

    // Getters & Setters
}

それらを使用してデータソースBeanを構築します。

@Autowired
private ClientADetails clientADetails;
@Autowired
private ClientBDetails clientBDetails;

private DataSource clientADatasource() {
EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();
return dbBuilder.setType(EmbeddedDatabaseType.H2)
.setName(clientADetails.getName())
.addScript(clientADetails.getScript())
.build();
}

private DataSource clientBDatasource() {
EmbeddedDatabaseBuilder dbBuilder = new EmbeddedDatabaseBuilder();
return dbBuilder.setType(EmbeddedDatabaseType.H2)
.setName(clientBDetails.getName())
.addScript(clientBDetails.getScript())
.build();
}

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の使用方法の例を見ていきました。 クライアントの概念を使用してソリューションを実装しました。各クライアントにはデータソースがあります。

そして、いつものように、例はGitHubを見つけることができます。