#226 - Add samples for new MongoDB aggregation operations in Ingalls.
This commit is contained in:
committed by
Oliver Gierke
parent
bc3623b532
commit
a138925b0d
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package example.springdata.mongodb.aggregation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArrayOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* Examples for Spring Books using the MongoDB Aggregation Framework. Data originates from Google's Book search.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see <a href=
|
||||
* "https://www.googleapis.com/books/v1/volumes?q=intitle:spring+framework">https://www.googleapis.com/books/v1/volumes?q=intitle:spring+framework</a>
|
||||
* @see <a href="/books.json>books.json</a>
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class SpringBooksIntegrationTests {
|
||||
|
||||
@Autowired MongoTemplate mongoTemplate;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
|
||||
if (mongoTemplate.count(new Query(), "books") == 0) {
|
||||
List<Object> books = (List) JSON.parse(
|
||||
StreamUtils.copyToString(new ClassPathResource("books.json").getInputStream(), StandardCharsets.UTF_8));
|
||||
|
||||
mongoTemplate.insert(books, "books");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Project Book titles.
|
||||
*/
|
||||
@Test
|
||||
public void shouldRetrieveOrderedBookTitles() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
sort(Direction.ASC, "volumeInfo.title"), //
|
||||
project().and("volumeInfo.title").as("title"));
|
||||
|
||||
AggregationResults<BookTitle> result = mongoTemplate.aggregate(aggregation, "books", BookTitle.class);
|
||||
|
||||
assertThat(result.getMappedResults()).extracting("title")
|
||||
.containsSequence("Aprende a Desarrollar con Spring Framework", "Beginning Spring", "Beginning Spring 2");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of books that were published by the particular publisher.
|
||||
*/
|
||||
@Test
|
||||
public void shouldRetrieveBooksPerPublisher() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
group("volumeInfo.publisher") //
|
||||
.count().as("count"), //
|
||||
sort(Direction.DESC, "count"), //
|
||||
project("count").and("_id").as("publisher"));
|
||||
|
||||
AggregationResults<BooksPerPublisher> result = mongoTemplate.aggregate(aggregation, "books",
|
||||
BooksPerPublisher.class);
|
||||
|
||||
assertThat(result).hasSize(27);
|
||||
assertThat(result).extracting("publisher").containsSequence("Apress", "Packt Publishing Ltd");
|
||||
assertThat(result).extracting("count").containsSequence(26, 22, 11);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of books that were published by the particular publisher with their titles.
|
||||
*/
|
||||
@Test
|
||||
public void shouldRetrieveBooksPerPublisherWithTitles() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
group("volumeInfo.publisher") //
|
||||
.count().as("count") //
|
||||
.addToSet("volumeInfo.title").as("titles"), //
|
||||
sort(Direction.DESC, "count"), //
|
||||
project("count", "titles").and("_id").as("publisher"));
|
||||
|
||||
AggregationResults<BooksPerPublisher> result = mongoTemplate.aggregate(aggregation, "books",
|
||||
BooksPerPublisher.class);
|
||||
|
||||
BooksPerPublisher booksPerPublisher = result.getMappedResults().get(0);
|
||||
|
||||
assertThat(booksPerPublisher.getPublisher()).isEqualTo("Apress");
|
||||
assertThat(booksPerPublisher.getCount()).isEqualTo(26);
|
||||
assertThat(booksPerPublisher.getTitles()).contains("Expert Spring MVC and Web Flow", "Pro Spring Boot");
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for Data-related books in their title and output the title and authors.
|
||||
*/
|
||||
@Test
|
||||
public void shouldRetrieveDataRelatedBooks() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
match(Criteria.where("volumeInfo.title").regex("data", "i")), //
|
||||
replaceRoot("volumeInfo"), //
|
||||
project("title", "authors"), //
|
||||
sort(Direction.ASC, "title"));
|
||||
|
||||
AggregationResults<BookAndAuthors> result = mongoTemplate.aggregate(aggregation, "books", BookAndAuthors.class);
|
||||
|
||||
BookAndAuthors bookAndAuthors = result.getMappedResults().get(1);
|
||||
|
||||
assertThat(bookAndAuthors.getTitle()).isEqualTo("Spring Data");
|
||||
assertThat(bookAndAuthors.getAuthors()).contains("Mark Pollack", "Oliver Gierke", "Thomas Risberg", "Jon Brisbin",
|
||||
"Michael Hunger");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of pages per author (and divide the number of pages by the number of authors).
|
||||
*/
|
||||
@Test
|
||||
public void shouldRetrievePagesPerAuthor() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
match(Criteria.where("volumeInfo.authors").exists(true)), //
|
||||
replaceRoot("volumeInfo"), //
|
||||
project("authors", "pageCount") //
|
||||
.and(ArithmeticOperators.valueOf("pageCount") //
|
||||
.divideBy(ArrayOperators.arrayOf("authors").length()))
|
||||
.as("pagesPerAuthor"),
|
||||
unwind("authors"), //
|
||||
group("authors") //
|
||||
.sum("pageCount").as("totalPageCount") //
|
||||
.sum("pagesPerAuthor").as("approxWritten"), //
|
||||
sort(Direction.DESC, "totalPageCount"));
|
||||
|
||||
AggregationResults<PagesPerAuthor> result = mongoTemplate.aggregate(aggregation, "books", PagesPerAuthor.class);
|
||||
|
||||
PagesPerAuthor pagesPerAuthor = result.getMappedResults().get(0);
|
||||
|
||||
assertThat(pagesPerAuthor.getAuthor()).isEqualTo("Josh Long");
|
||||
assertThat(pagesPerAuthor.getTotalPageCount()).isEqualTo(1892);
|
||||
assertThat(pagesPerAuthor.getApproxWritten()).isEqualTo(573);
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize books by their page count into buckets.
|
||||
*/
|
||||
@Test
|
||||
public void shouldCategorizeBooksInBuckets() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
replaceRoot("volumeInfo"), //
|
||||
match(Criteria.where("pageCount").exists(true)),
|
||||
bucketAuto("pageCount", 10) //
|
||||
.withGranularity(Granularities.SERIES_1_2_5) //
|
||||
.andOutput("title").push().as("titles") //
|
||||
.andOutput("titles").count().as("count"));
|
||||
|
||||
AggregationResults<BookFacetPerPage> result = mongoTemplate.aggregate(aggregation, "books", BookFacetPerPage.class);
|
||||
|
||||
List<BookFacetPerPage> mappedResults = result.getMappedResults();
|
||||
|
||||
BookFacetPerPage facet_20_to_100_pages = mappedResults.get(0);
|
||||
assertThat(facet_20_to_100_pages.getMin()).isEqualTo(20);
|
||||
assertThat(facet_20_to_100_pages.getMax()).isEqualTo(100);
|
||||
assertThat(facet_20_to_100_pages.getCount()).isEqualTo(12);
|
||||
|
||||
BookFacetPerPage facet_100_to_500_pages = mappedResults.get(1);
|
||||
assertThat(facet_100_to_500_pages.getMin()).isEqualTo(100);
|
||||
assertThat(facet_100_to_500_pages.getMax()).isEqualTo(500);
|
||||
assertThat(facet_100_to_500_pages.getCount()).isEqualTo(63);
|
||||
assertThat(facet_100_to_500_pages.getTitles()).contains("Spring Data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a multi-faceted aggregation to get buckets by price (1-10, 10-50, 50-100 EURO) and by the first letter of the
|
||||
* author name.
|
||||
*/
|
||||
@Test
|
||||
public void shouldCategorizeInMultipleFacetsByPriceAndAuthor() {
|
||||
|
||||
Aggregation aggregation = newAggregation( //
|
||||
match(Criteria.where("volumeInfo.authors").exists(true).and("volumeInfo.publisher").exists(true)),
|
||||
facet() //
|
||||
.and(match(Criteria.where("saleInfo.listPrice").exists(true)), //
|
||||
replaceRoot("saleInfo"), //
|
||||
bucket("listPrice.amount") //
|
||||
.withBoundaries(1, 10, 50, 100))
|
||||
.as("prices") //
|
||||
|
||||
.and(unwind("volumeInfo.authors"), //
|
||||
replaceRoot("volumeInfo"), //
|
||||
match(Criteria.where("authors").not().size(0)), //
|
||||
project() //
|
||||
.andExpression("substrCP(authors, 0, 1)").as("startsWith") //
|
||||
.and("authors").as("author"), //
|
||||
bucketAuto("startsWith", 10) //
|
||||
.andOutput("author").push().as("authors") //
|
||||
).as("authors"));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, "books", DBObject.class);
|
||||
|
||||
DBObject uniqueMappedResult = result.getUniqueMappedResult();
|
||||
|
||||
assertThat((List) uniqueMappedResult.get("prices")).hasSize(3);
|
||||
assertThat((List) uniqueMappedResult.get("authors")).hasSize(8);
|
||||
}
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
static class BookTitle {
|
||||
String title;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
static class BooksPerPublisher {
|
||||
String publisher;
|
||||
int count;
|
||||
List<String> titles;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
static class BookAndAuthors {
|
||||
String title;
|
||||
List<String> authors;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
static class PagesPerAuthor {
|
||||
@Id String author;
|
||||
int totalPageCount;
|
||||
int approxWritten;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
static class BookFacetPerPage {
|
||||
int min;
|
||||
int max;
|
||||
int count;
|
||||
List<String> titles;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
spring.mongodb.embedded.version=3.4.1
|
||||
6220
mongodb/aggregation/src/test/resources/books.json
Normal file
6220
mongodb/aggregation/src/test/resources/books.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user