Spring Data MongoDB:予測と集計
1. 概要
Spring Data MongoDBは、MongoDBネイティブクエリ言語にシンプルで高レベルの抽象化を提供します。 この記事では、we will explore the support for Projections and Aggregation framework.
このトピックを初めて使用する場合は、紹介記事Introduction to Spring Data MongoDBを参照してください。
2. 投影
MongoDBでは、プロジェクションはデータベースからドキュメントの必須フィールドのみを取得する方法です。 これにより、データベースサーバーからクライアントに転送する必要があるデータの量が減るため、パフォーマンスが向上します。
Spring Data MongDBを使用すると、プロジェクションはMongoTemplateとMongoRepository.の両方で使用できます。
先に進む前に、使用するデータモデルを見てみましょう。
@Document
public class User {
@Id
private String id;
private String name;
private Integer age;
// standard getters and setters
}
2.1. MongoTemplateを使用したプロジェクション
Fieldクラスのinclude()メソッドとexclude()メソッドは、それぞれフィールドを含めたり除外したりするために使用されます。
Query query = new Query();
query.fields().include("name").exclude("id");
List john = mongoTemplate.find(query, User.class);
これらのメソッドを連結して、複数のフィールドを含めたり除外したりできます。 @Id(データベースでは_id)としてマークされたフィールドは、明示的に除外されない限り、常にフェッチされます。
レコードがプロジェクションでフェッチされる場合、除外されるフィールドはモデルクラスインスタンスのnullです。 フィールドがプリミティブ型またはそのラッパークラスの場合、除外されたフィールドの値はプリミティブ型のデフォルト値です。
たとえば、Stringはnull、int /Integerは0、boolean /Booleanはfalse。
したがって、上記の例では、nameフィールドはJohn、idはnull、ageは0.になります。
2.2. MongoRepositoryを使用したプロジェクション
MongoRepositoriesを使用している間、@QueryアノテーションのfieldsはJSON形式で定義できます。
@Query(value="{}", fields="{name : 1, _id : 0}")
List findNameAndExcludeId();
結果は、MongoTemplateを使用した場合と同じになります。 value=”\{}”はフィルターがないことを示しているため、すべてのドキュメントがフェッチされます。
3. Aggregation
MongoDBの集計は、データを処理し、計算結果を返すために構築されました。 データは段階的に処理され、1つのステージの出力は次のステージへの入力として提供されます。 変換を適用し、データを段階的に計算するこの機能により、集計は分析のための非常に強力なツールになります。
Spring Data MongoDBは、集計クエリをラップする3つのクラスAggregation、個々のパイプラインステージをラップするAggregationOperation、およびによって生成される結果のコンテナーであるAggregationResultsを使用して、ネイティブ集計クエリの抽象化を提供します。集約。
実行して集計するには、まず、Aggregationクラスで静的ビルダーメソッドを使用して集計パイプラインを作成し、次にAggregationクラスでnewAggregation()メソッドを使用してAggregationのインスタンスを作成します。最後に、MongoTemplateを使用して集計を実行します。
MatchOperation matchStage = Aggregation.match(new Criteria("foo").is("bar"));
ProjectionOperation projectStage = Aggregation.project("foo", "bar.baz");
Aggregation aggregation
= Aggregation.newAggregation(matchStage, projectStage);
AggregationResults output
= mongoTemplate.aggregate(aggregation, "foobar", OutType.class);
MatchOperationとProjectionOperationの両方がAggregationOperationを実装していることに注意してください。 他の集約パイプラインにも同様の実装があります。 OutTypeは、期待される出力のデータモデルです。
次に、いくつかの例とその説明を見て、主要な集約パイプラインと演算子を説明します。
この記事で使用するデータセットには、MongoDB repositoryからダウンロードできる米国内のすべての郵便番号の詳細がリストされています。
testデータベースのzipsというコレクションにインポートした後のサンプルドキュメントを見てみましょう。
{
"_id" : "01001",
"city" : "AGAWAM",
"loc" : [
-72.622739,
42.070206
],
"pop" : 15338,
"state" : "MA"
}
簡単にするため、またコードを簡潔にするために、次のコードスニペットでは、Aggregationクラスのすべてのstaticメソッドが静的にインポートされると想定します。
3.1. 人口の降順で人口が1,000万人を超えるすべての州を取得する
ここでは、3つのパイプラインがあります。
-
すべての郵便番号の人口を合計する$groupステージ
-
人口が1,000万人を超える州を除外する$matchステージ
-
人口の降順ですべてのドキュメントを並べ替える$sortステージ
期待される出力には、状態としてのフィールド_idと、状態の総人口を含むフィールドstatePopがあります。 このためのデータモデルを作成し、集計を実行してみましょう。
public class StatePoulation {
@Id
private String state;
private Integer statePop;
// standard getters and setters
}
@Idアノテーションは、_idフィールドを出力からモデルのstateにマップします。
GroupOperation groupByStateAndSumPop = group("state")
.sum("pop").as("statePop");
MatchOperation filterStates = match(new Criteria("statePop").gt(10000000));
SortOperation sortByPopDesc = sort(new Sort(Direction.DESC, "statePop"));
Aggregation aggregation = newAggregation(
groupByStateAndSumPop, filterStates, sortByPopDesc);
AggregationResults result = mongoTemplate.aggregate(
aggregation, "zips", StatePopulation.class);
AggregationResultsクラスはIterableを実装しているため、それを繰り返し処理して結果を出力できます。
出力データモデルが不明な場合は、標準のMongoDBクラスDocumentを使用できます。
3.2. 平均的な都市人口で最小の州を取得する
この問題には、次の4つの段階が必要です。
-
$groupは、各都市の総人口を合計します
-
各州の平均人口を計算するための$group
-
$sortは、州を平均都市人口で昇順で並べ替える段階
-
$limitは、平均都市人口が最も少ない最初の州を取得します
必ずしも必須ではありませんが、追加の$projectステージを使用して、outStatePopulationデータモデルに従ってドキュメントを再フォーマットします。
GroupOperation sumTotalCityPop = group("state", "city")
.sum("pop").as("cityPop");
GroupOperation averageStatePop = group("_id.state")
.avg("cityPop").as("avgCityPop");
SortOperation sortByAvgPopAsc = sort(new Sort(Direction.ASC, "avgCityPop"));
LimitOperation limitToOnlyFirstDoc = limit(1);
ProjectionOperation projectToMatchModel = project()
.andExpression("_id").as("state")
.andExpression("avgCityPop").as("statePop");
Aggregation aggregation = newAggregation(
sumTotalCityPop, averageStatePop, sortByAvgPopAsc,
limitToOnlyFirstDoc, projectToMatchModel);
AggregationResults result = mongoTemplate
.aggregate(aggregation, "zips", StatePopulation.class);
StatePopulation smallestState = result.getUniqueMappedResult();
この例では、最終段階で出力ドキュメントの数を1に制限しているため、結果には1つのドキュメントしか存在しないことがすでにわかっています。 そのため、getUniqueMappedResult()を呼び出して、必要なStatePopulationインスタンスを取得できます。
もう1つの注意点は、_idを状態にマップするために@Idアノテーションに依存する代わりに、投影段階で明示的に行ったことです。
3.3. 最大および最小の郵便番号で州を取得する
この例では、3つのステージが必要です。
-
$groupは、各州の郵便番号の数をカウントします
-
$sortは、郵便番号の数で州を並べ替えます
-
$firstおよび$last演算子を使用して、最大および最小の郵便番号で状態を検索する$group
GroupOperation sumZips = group("state").count().as("zipCount");
SortOperation sortByCount = sort(Direction.ASC, "zipCount");
GroupOperation groupFirstAndLast = group().first("_id").as("minZipState")
.first("zipCount").as("minZipCount").last("_id").as("maxZipState")
.last("zipCount").as("maxZipCount");
Aggregation aggregation = newAggregation(sumZips, sortByCount, groupFirstAndLast);
AggregationResults result = mongoTemplate
.aggregate(aggregation, "zips", Document.class);
Document document= result.getUniqueMappedResult();
ここではモデルを使用していませんが、MongoDBドライバーですでに提供されているDocumentを使用しています。
4. 結論
この記事では、Spring Data MongoDBのプロジェクションを使用して、MongoDBのドキュメントの指定されたフィールドを取得する方法を学びました。
また、Spring DataでのMongoDB集計フレームワークのサポートについても学びました。 グループ、プロジェクト、ソート、制限、および一致の主要な集約フェーズを取り上げ、その実用的なアプリケーションの例をいくつか見てきました。 完全なソースコードはavailable over on GitHubです。