Spring 5では、 webflux を使用したREST APIのリアクティブプログラミングスタイルを導入しています。私自身はかなり新しいので、データベースへの同期呼び出しをFlux
またはMono
にラップするのは理にかなっているのだろうか?はいの場合、これはそれを行う方法です:
@RestController
public class HomeController {
private MeasurementRepository repository;
public HomeController(MeasurementRepository repository){
this.repository = repository;
}
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L)));
}
}
非同期CrudRepositoryのようなものはありますか?見つかりませんでした。
1つのオプションは、完全に非ブロッキングの代替SQLクライアントを使用することです。以下に例を示します。 https://github.com/mauricio/postgresql-async または https://github.com/finagle/roc 。もちろん、これらのドライバーはいずれもデータベースベンダーによって公式にサポートされていません。また、機能は、HibernateやjOOQなどの成熟したJDBCベースの抽象化と比較して、はるかに魅力的ではありません。
別のアイデアは、Scala world。から来ました。アイデアは、ブロッキング呼び出しと非ブロッキング呼び出しを一緒に混合しないように、ブロッキング呼び出しを分離されたThreadPoolにディスパッチすることです。スレッドを作成し、CPUが最適化の可能性があるメイン実行コンテキストでノンブロッキングタスクを処理できるようにします。実際にブロッキングしているSpring Data JPAなどのJDBCベースの実装があると仮定すると、専用スレッドで実行を非同期にしてディスパッチできますプール。
@RestController
public class HomeController {
private final MeasurementRepository repository;
private final Scheduler scheduler;
public HomeController(MeasurementRepository repository, @Qualifier("jdbcScheduler") Scheduler scheduler) {
this.repository = repository;
this.scheduler = scheduler;
}
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Mono.fromCallable(() -> repository.findByFromDateGreaterThanEqual(new Date(1486980000L))).publishOn(scheduler);
}
}
JDBCのスケジューラは、接続数と同じサイズカウントの専用スレッドプールを使用して構成する必要があります。
@Configuration
public class SchedulerConfiguration {
private final Integer connectionPoolSize;
public SchedulerConfiguration(@Value("${spring.datasource.maximum-pool-size}") Integer connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
@Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(Executors.newFixedThreadPool(connectionPoolSize));
}
}
ただし、このアプローチには困難があります。主なものはトランザクション管理です。 JDBCでは、トランザクションは単一のJava.sql.Connection内でのみ可能です。 1つのトランザクションで複数の操作を行うには、接続を共有する必要があります。それらの間にいくつかの計算を行いたい場合、接続を維持する必要があります。これはあまり効果的ではありません。その間、計算を行う間、限られた数の接続をアイドル状態に保つからです。
この非同期JDBCラッパーの考え方は新しいものではなく、Scala library Slick 3で既に実装されています。最後に、非ブロッキングJDBCはJavaロードマップ:2016年9月にJavaOneで発表されたように、Java 10。
これに基づいて ブログ 次のようにスニペットを書き換える必要があります
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Flux.defer(() -> Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L))))
.subscribeOn(Schedulers.elastic());
}
Springデータは、MongoおよびCassandraのリアクティブリポジトリインターフェイスをサポートします。
Spring data MongoDb Reactive Interface
Spring Data MongoDBは、Project ReactorおよびRxJava 1のリアクティブタイプでリアクティブリポジトリサポートを提供します。リアクティブAPIは、リアクティブ型間のリアクティブ型変換をサポートしています。
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {
Flux<Person> findByLastname(String lastname);
@Query("{ 'firstname': ?0, 'lastname': ?1}")
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
// Accept parameter inside a reactive type for deferred execution
Flux<Person> findByLastname(Mono<String> lastname);
Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);
@InfiniteStream // Use a tailable cursor
Flux<Person> findWithTailableCursorBy();
}
public interface RxJava1PersonRepository extends RxJava1CrudRepository<Person, String> {
Observable<Person> findByLastname(String lastname);
@Query("{ 'firstname': ?0, 'lastname': ?1}")
Single<Person> findByFirstnameAndLastname(String firstname, String lastname);
// Accept parameter inside a reactive type for deferred execution
Observable<Person> findByLastname(Single<String> lastname);
Single<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);
@InfiniteStream // Use a tailable cursor
Observable<Person> findWithTailableCursorBy();
}
FluxまたはMonoを取得しても、必ずしも専用のスレッドで実行されるわけではありません。代わりに、ほとんどのオペレーターは、前のオペレーターが実行したスレッドで作業を続けます。指定しない限り、最上位の演算子(ソース)自体は、subscribe()呼び出しが行われたスレッドで実行されます。
使用するブロッキング永続性API(JPA、JDBC)またはネットワークAPIがある場合、少なくとも一般的なアーキテクチャにはSpring MVCが最適です。 ReactorとRxJavaの両方で別のスレッドでブロッキング呼び出しを実行することは技術的には可能ですが、非ブロッキングWebスタックを最大限に活用することはできません。
それで... 同期のブロッキング呼び出しをどのようにラップしますか?
Callable
を使用して実行を延期します。また、Schedulers.elastic
を使用する必要があります。これは、他のリソースを使用せずにブロッキングリソースを待機する専用のスレッドを作成するためです。
例:
Mono.fromCallable(() -> blockingRepository.save())
.subscribeOn(Schedulers.elastic());