web-dev-qa-db-ja.com

Spring Webfluxとデータベースからの読み取り

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のようなものはありますか?見つかりませんでした。

23
Lukasz

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。

27

これに基づいて ブログ 次のようにスニペットを書き換える必要があります

@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
    return Flux.defer(() -> Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L))))
           .subscribeOn(Schedulers.elastic());
}
7

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();
}
5
yousafsajjad

FluxまたはMonoを取得しても、必ずしも専用のスレッドで実行されるわけではありません。代わりに、ほとんどのオペレーターは、前のオペレーターが実行したスレッドで作業を続けます。指定しない限り、最上位の演算子(ソース)自体は、subscribe()呼び出しが行われたスレッドで実行されます。

使用するブロッキング永続性API(JPA、JDBC)またはネットワークAPIがある場合、少なくとも一般的なアーキテクチャにはSpring MVCが最適です。 ReactorとRxJavaの両方で別のスレッドでブロッキング呼び出しを実行することは技術的には可能ですが、非ブロッキングWebスタックを最大限に活用することはできません。

それで... 同期のブロッキング呼び出しをどのようにラップしますか?

Callableを使用して実行を延期します。また、Schedulers.elasticを使用する必要があります。これは、他のリソースを使用せずにブロッキングリソースを待機する専用のスレッドを作成するためです。

  • Schedulers.immediate():現在のスレッド。
  • Schedulers.single():単一の再利用可能なスレッド。
  • Schedulers.newSingle():呼び出しごとの専用スレッド。
  • Schedulers.elastic():エラスティックスレッドプール。必要に応じて新しいワーカープールを作成し、アイドルプールを再利用します。これは、たとえばI/Oブロッキングの作業に適しています。
  • Schedulers.parallel():並列作業用に調整されたワーカーの固定プール。

例:

Mono.fromCallable(() -> blockingRepository.save())
        .subscribeOn(Schedulers.elastic());
3
kkd927