例はここにあります: https://github.com/afedulov/routing-data-ソース 。
Springは、 AbstractRoutingDatasource
と呼ばれるデータソースのバリエーションを提供します 。これは、標準のDataSource実装の代わりに使用でき、実行時に各操作に使用する具体的なDataSourceを決定するメカニズムを可能にします。あなたがする必要があるのはそれを拡張し、抽象的な defineCurrentLookupKey
の実装を提供することです 方法。これは、具体的なデータソースを決定するためのカスタムロジックを実装する場所です。返されたオブジェクトはルックアップキーとして機能します。これは通常、文字列または列挙型であり、Spring構成で修飾子として使用されます(詳細は以下を参照)。
package website.fedulov.routing.RoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
そのDbContextHolderオブジェクトとは何であり、どのDataSource識別子を返すかをどのように知るのか疑問に思われるかもしれません。 defineCurrentLookupKey
であることに注意してください TransactionsManagerが接続を要求するたびに、メソッドが呼び出されます。各トランザクションは個別のスレッドに「関連付けられている」ことを覚えておくことが重要です。より正確には、TransactionsManagerはConnectionを現在のスレッドにバインドします。したがって、さまざまなトランザクションをさまざまなターゲットデータソースにディスパッチするには、すべてのスレッドが、どのデータソースが使用されるようになっているのかを確実に識別できるようにする必要があります。これにより、ThreadLocal変数を利用して、特定のデータソースをスレッドにバインドし、したがってトランザクションにバインドするのが自然になります。これがその方法です:
public enum DbType {
MASTER,
REPLICA1,
}
public class DbContextHolder {
private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
public static void setDbType(DbType dbType) {
if(dbType == null){
throw new NullPointerException();
}
contextHolder.set(dbType);
}
public static DbType getDbType() {
return (DbType) contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
ご覧のとおり、列挙型をキーとして使用することもでき、Springが名前に基づいて列挙型を正しく解決します。関連するデータソースの構成とキーは次のようになります。
....
<bean id="dataSource" class="website.fedulov.routing.RoutingDataSource">
<property name="targetDataSources">
<map key-type="com.sabienzia.routing.DbType">
<entry key="MASTER" value-ref="dataSourceMaster"/>
<entry key="REPLICA1" value-ref="dataSourceReplica"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceMaster"/>
</bean>
<bean id="dataSourceMaster" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="${db.master.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<bean id="dataSourceReplica" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="${db.replica.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
この時点で、次のようなことをしていることに気付くかもしれません:
@Service
public class BookService {
private final BookRepository bookRepository;
private final Mapper mapper;
@Inject
public BookService(BookRepository bookRepository, Mapper mapper) {
this.bookRepository = bookRepository;
this.mapper = mapper;
}
@Transactional(readOnly = true)
public Page<BookDTO> getBooks(Pageable p) {
DbContextHolder.setDbType(DbType.REPLICA1); // <----- set ThreadLocal DataSource lookup key
// all connection from here will go to REPLICA1
Page<Book> booksPage = callActionRepo.findAll(p);
List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
DbContextHolder.clearDbType(); // <----- clear ThreadLocal setting
return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
}
...//other methods
これで、使用するデータソースを制御し、必要に応じてリクエストを転送できます。いいね!
...それともそうですか?まず第一に、魔法のDbContextHolderへの静的メソッド呼び出しは本当に目立ちます。それらはビジネスロジックに属していないように見えます。そして、彼らはしません。それらは目的を伝えないだけでなく、壊れやすくエラーが発生しやすいように見えます(dbTypeのクリーンアップを忘れてはどうでしょうか)。そして、setDbTypeとcleanDbTypeの間に例外がスローされた場合はどうなりますか?無視することはできません。 dbTypeをリセットすることを絶対に確認する必要があります。そうしないと、ThreadPoolに返されたスレッドが「壊れた」状態になり、次の呼び出しでレプリカに書き込もうとする可能性があります。したがって、これが必要です:
@Transactional(readOnly = true)
public Page<BookDTO> getBooks(Pageable p) {
try{
DbContextHolder.setDbType(DbType.REPLICA1); // <----- set ThreadLocal DataSource lookup key
// all connection from here will go to REPLICA1
Page<Book> booksPage = callActionRepo.findAll(p);
List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
DbContextHolder.clearDbType(); // <----- clear ThreadLocal setting
} catch (Exception e){
throw new RuntimeException(e);
} finally {
DbContextHolder.clearDbType(); // <----- make sure ThreadLocal setting is cleared
}
return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
}
Yikes > _ <
!これは、私がすべての読み取り専用メソッドに入れたいもののようには見えません。もっと上手くできますか?もちろん! 「メソッドの最初で何かを実行し、最後で何かを実行する」というこのパターンは、ベルを鳴らす必要があります。救助の側面!
残念ながら、この投稿はすでに長すぎてカスタムアスペクトのトピックをカバーできません。この