春のご案内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]にあります。