最近、次のようなオブジェクトのフィルタリングにストリームを使用するいくつかのプロジェクトに多くのコードが表示されます。
library.stream()
.map(book -> book.getAuthor())
.filter(author -> author.getAge() >= 50)
.map(Author::getSurname)
.map(String::toUpperCase)
.distinct()
.limit(15)
.collect(toList()));
既にフィルターされた結果を返すデータベースへの直接のHQL/SQLクエリの代わりにそれを使用する利点はありますか?.
2番目のアプローチははるかに速くありませんか?
データが元々DBからのものである場合、すべてをフェッチしてローカルでフィルタリングするよりも、DBでフィルタリングする方が適切です。
まず、データベース管理システムはフィルタリングに優れており、主な業務の一部であるため、最適化されています。インデックスを使用して、フィルタリングを高速化することもできます。
次に、多くのレコードをフェッチして送信し、データをオブジェクトに非整列化してローカルフィルタリングを実行するときに大量のレコードを破棄することは、帯域幅とコンピューティングリソースの浪費です。
一見すると、ストリームを並行して実行できます。 justparallelStream()
を使用するようにコードを変更します。 (免責事項:もちろん、ストリームタイプを変更するだけで正しい結果が得られるかどうかは、特定のコンテキストに依存しますが、そうです簡単です。
次に、ラムダ式を使用するように「招待」をストリーミングします。そして、それらは次に invoke_dynamic バイトコード命令の使用につながります。 「古い」種類のコードを作成する場合と比較して、パフォーマンスの利点が得られる場合があります。 (そして誤解を明確にするために:invoke_dynamicはストリームではなくラムダのプロパティです!)
これらは、今日(一般的な観点から)「ストリーム」ソリューションを好む理由です。
それを超えて:それは本当に依存します...あなたの入力例を見てみましょう。これは、通常のJava POJOを処理しているように見えます。これは、何らかのコレクション内のメモリに既に存在します。このようなオブジェクトをメモリ内で処理します直接オフプロセスデータベースにアクセスしてそこで作業するよりも間違いなく高速です。
しかし、もちろん:上記の呼び出しがbook.getAuthor()
のような場合、「詳細な調査」が行われ、実際に基礎となるデータベースと通信します。その場合、「1つのクエリですべてを実行する」ことでパフォーマンスが向上する可能性があります。
最初に理解することは、このコードだけではデータベースに対してどのステートメントが発行されているのかを判断できないことです。すべてのフィルタリング、制限、マッピングが収集され、collect
が呼び出されると、そのすべての情報を使用して、一致するSQLステートメント(または使用されるクエリ言語)が作成され、データベース。
これを念頭に置いて、streamlike APIが使用される理由はたくさんあります。
ヒップです。ほとんどのJava=開発者にとって、ストリームとラムダはまだかなり新しいので、使用するときはクールに感じます。
最初の段落のようなものを使用すると、実際には、クエリステートメントを作成するためのNice DSLが作成されます。 Scalas Slick と 。Net LINQ 私が知っている初期の例ですが、私が生まれるずっと前に誰かがLISPでこのようなものを構築していると思います。
ストリームは reactive stream であり、非ブロッキングAPIをカプセル化する可能性があります。これらのAPIは、結果を待っている間、スレッドなどのリソースをブロックすることを強制しないため、本当に素晴らしいです。それらを使用するには、大量のコールバックを使用するか、より優れたストリームベースのAPIを使用して結果を処理する必要があります。
彼らは命令コードを読む方がいいです。おそらく、ストリームで行われた処理は、SQLで[簡単に/作者が]行うことができません。したがって、代替案はSQLとJava(または使用している言語))ではなく、命令型Javaまたは "機能的" Javaです。後者はしばしばより良い。
したがって、このようなAPIを使用するのには十分な理由があります。
以上のことから、ほとんどの場合、アプリケーションをデータベースにオフロードできるときに、アプリケーションで並べ替えやフィルタリングなどを行うことはお勧めできません。私が現在考えることができる唯一の例外は、データベースへのラウンドトリップ全体をスキップできる場合です。これは、既にローカルで(キャッシュなどに)結果があるためです。
さて、あなたの質問は理想的には-DBで削減/フィルタリング操作を行うか、すべてのレコードをフェッチしてそれをJava Streamsを使用して?
答えは簡単ではなく、「具体的な」答えを与える統計はすべてのケースに一般化されるわけではありません。
あなたが話している操作DBはそのために設計されているため、DB自体で実行する方が良いです。データの処理が非常に高速です。もちろん、通常はリレーショナルデータベースの場合、独立したトランザクションがデータの不整合を引き起こさないようにするために使用される「簿記とロック」がいくつかありますが、それでもDBはフィルタリングでかなり良い仕事をしますデータ、特に大きなデータセット。
DBではなくJavaコードでデータをフィルター処理したい場合の1つのケースは、同じデータから異なる機能をフィルター処理する必要がある場合です。たとえば、今のところ、作成者の姓のみを取得しています。 。著者が書いたすべての本、著者の年齢、著者の子供、出生地などを取得したい場合は、DBから「読み取り専用」コピーを1つだけ取得し、並列ストリームを使用して異なるものにすることは理にかなっています。同じデータセットからの情報。
特定のシナリオで測定され、証明されない限り、どちらかが良いか、同様に悪いかもしれません。 通常がこの種のクエリをデータベースに取りたい理由は、(とりわけ)次の理由によります。
DBは、あなたのJavaプロセスよりもはるかに大きなデータを処理できます
データベース内のクエリにインデックスを付けることができます(クエリを大幅に高速化します)
一方、データが小さい場合は、Stream
を使用する方法が効果的です。このようなストリームパイプラインの記述は非常に読みやすい(一度talkストリームで十分です)です。
Hibernateやその他のORMは、開発者が特定の書き込みの順序付けをフレームワークにオフロードすることがほとんどないため、読み取りよりもエンティティの書き込みに通常より便利です。
一方、読み取りとレポートについては(また、ここでDBについて話していることを考えると)、SQLクエリの方が優れている可能性があります。これは、間にフレームワークがなく、クエリのパフォーマンスを調整できるためです。選択したフレームワークの観点からではなく、このクエリを呼び出すデータベースのデータベースです。これにより、チューニングを行う方法に柔軟性が生まれます。