diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml b/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml index c48525673a..3f144bae54 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml +++ b/persistence-modules/spring-boot-persistence-mongodb-3/pom.xml @@ -9,27 +9,12 @@ com.baeldung - parent-boot-2 + parent-boot-3 0.0.1-SNAPSHOT - ../../parent-boot-2 + ../../parent-boot-3 - - org.mongodb - mongodb-driver-sync - ${mongodb-driver.version} - - - org.mongodb - mongodb-driver-core - ${mongodb-driver.version} - - - org.mongodb - bson - ${mongodb-driver.version} - org.springframework.boot spring-boot-starter-web @@ -37,16 +22,6 @@ org.springframework.boot spring-boot-starter-data-mongodb - - - org.mongodb - mongodb-driver-sync - - - org.mongodb - mongodb-driver-core - - org.mongodb @@ -57,12 +32,13 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo test + ${de.flapdoodle.embed.mongo.version} - 1.7.3 - 4.9.1 + com.baeldung.boot.atlassearch.MongoDbAtlasSearchApplication + 1.8.0 + 4.9.2 - diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/MongoDbAtlasSearchApplication.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/MongoDbAtlasSearchApplication.java new file mode 100644 index 0000000000..afaf524829 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/MongoDbAtlasSearchApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.boot.atlassearch; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MongoDbAtlasSearchApplication { + + public static void main(String... args) { + SpringApplication.run(MongoDbAtlasSearchApplication.class, args); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/config/IndexConfig.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/config/IndexConfig.java new file mode 100644 index 0000000000..cb4fae89e4 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/config/IndexConfig.java @@ -0,0 +1,22 @@ +package com.baeldung.boot.atlassearch.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class IndexConfig { + + @Value("${com.baeldung.atlas-search.index.query}") + private String queryIndex; + + @Value("${com.baeldung.atlas-search.index.facet}") + private String facetIndex; + + public String getFacetIndex() { + return facetIndex; + } + + public String getQueryIndex() { + return queryIndex; + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/service/MovieAtlasSearchService.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/service/MovieAtlasSearchService.java new file mode 100644 index 0000000000..55d47759d5 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/service/MovieAtlasSearchService.java @@ -0,0 +1,183 @@ +package com.baeldung.boot.atlassearch.service; + +import static com.mongodb.client.model.Aggregates.facet; +import static com.mongodb.client.model.Aggregates.limit; +import static com.mongodb.client.model.Aggregates.project; +import static com.mongodb.client.model.Aggregates.replaceWith; +import static com.mongodb.client.model.Aggregates.search; +import static com.mongodb.client.model.Aggregates.searchMeta; +import static com.mongodb.client.model.Aggregates.skip; +import static com.mongodb.client.model.Projections.excludeId; +import static com.mongodb.client.model.Projections.fields; +import static com.mongodb.client.model.Projections.include; +import static com.mongodb.client.model.Projections.metaSearchScore; +import static com.mongodb.client.model.search.SearchCount.total; +import static com.mongodb.client.model.search.SearchFacet.combineToBson; +import static com.mongodb.client.model.search.SearchFacet.numberFacet; +import static com.mongodb.client.model.search.SearchFacet.stringFacet; +import static com.mongodb.client.model.search.SearchOperator.compound; +import static com.mongodb.client.model.search.SearchOperator.numberRange; +import static com.mongodb.client.model.search.SearchOperator.of; +import static com.mongodb.client.model.search.SearchOperator.text; +import static com.mongodb.client.model.search.SearchOptions.searchOptions; +import static com.mongodb.client.model.search.SearchPath.fieldPath; +import static java.util.Arrays.asList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.logging.log4j.LogManager; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Service; + +import com.baeldung.boot.atlassearch.config.IndexConfig; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Facet; +import com.mongodb.client.model.search.SearchScore; + +@Service +public class MovieAtlasSearchService { + + @Autowired + private IndexConfig config; + + private final MongoCollection collection; + + public MovieAtlasSearchService(MongoTemplate mongoTemplate) { + MongoDatabase database = mongoTemplate.getDb(); + this.collection = database.getCollection("movies"); + } + + public static void debug(List pipeline) { + StringBuilder builder = new StringBuilder("["); + final AtomicBoolean first = new AtomicBoolean(true); + pipeline.forEach(stage -> { + builder.append((first.get() ? "" : ",") + + stage.toBsonDocument() + ); + + first.set(false); + }); + builder.append("]"); + + LogManager.getLogger(MovieAtlasSearchService.class) + .debug(builder.toString()); + } + + public Document late90sMovies(int skip, int limit, String keywords, SearchScore modifier) { + List pipeline = asList( + search( + compound() + .must(asList( + numberRange( + fieldPath("year")) + .gteLt(1995, 2000) + )) + .should(asList( + text( + fieldPath("fullplot"), keywords + ) + .score(modifier) + )), + searchOptions() + .index(config.getQueryIndex()) + ), + project(fields( + excludeId(), + include("title", "year", "fullplot", "imdb.rating"), + metaSearchScore("score") + )), + facet( + new Facet("rows", + skip(skip), + limit(limit) + ), + new Facet("totalRows", + replaceWith("$$SEARCH_META"), + limit(1) + ) + ) + ); + + debug(pipeline); + return collection.aggregate(pipeline) + .first(); + } + + public Document countLate90sMovies(String keywords) { + List pipeline = asList( + searchMeta( + compound() + .must(asList( + numberRange( + fieldPath("year")) + .gteLt(1995, 2000), + text( + fieldPath("fullplot"), keywords + ) + )), + searchOptions() + .index(config.getQueryIndex()) + .count(total()) + ) + ); + + debug(pipeline); + return collection.aggregate(pipeline) + .first(); + } + + public Collection moviesByKeywords(String keywords) { + List pipeline = asList( + search( + text( + fieldPath("fullplot"), keywords + ), + searchOptions() + .index(config.getQueryIndex()) + ), + project(fields( + excludeId(), + include("title", "year", "fullplot", "imdb.rating") + )) + ); + + debug(pipeline); + return collection.aggregate(pipeline) + .into(new ArrayList()); + } + + public Document genresThroughTheDecades(String genre) { + List pipeline = asList( + searchMeta(of( + new Document("facet", + new Document("operator", + text( + fieldPath("genres"), genre + ) + ).append("facets", combineToBson(asList( + stringFacet("genresFacet", + fieldPath("genres") + ).numBuckets(5), + numberFacet("yearFacet", + fieldPath("year"), + asList(1900, 1930, 1960, 1990, 2020) + ) + ))) + )), + searchOptions() + .index(config.getFacetIndex()) + ) + ); + + debug(pipeline); + return collection.aggregate(pipeline) + .first(); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/web/MovieAtlasSearchController.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/web/MovieAtlasSearchController.java new file mode 100644 index 0000000000..4c41915347 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/java/com/baeldung/boot/atlassearch/web/MovieAtlasSearchController.java @@ -0,0 +1,48 @@ +package com.baeldung.boot.atlassearch.web; + +import java.util.Collection; + +import org.bson.Document; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.boot.atlassearch.service.MovieAtlasSearchService; +import com.mongodb.client.model.search.SearchScore; + +@RestController +@RequestMapping("/movies") +public class MovieAtlasSearchController { + + @Autowired + private MovieAtlasSearchService service; + + @GetMapping("with/{keywords}") + Collection getMoviesWithKeywords(@PathVariable String keywords) { + return service.moviesByKeywords(keywords); + } + + @GetMapping("90s/with/{keywords}/count") + Document getCount90sMoviesWithKeywords(@PathVariable String keywords) { + return service.countLate90sMovies(keywords); + } + + @GetMapping("90s/{skip}/{limit}/with/{keywords}") + Document getMoviesUsingScoreBoost(@PathVariable int skip, @PathVariable int limit, @PathVariable String keywords) { + return service.late90sMovies(skip, limit, keywords, SearchScore.boost(2)); + } + + @PostMapping("90s/{skip}/{limit}/with/{keywords}") + Document getMoviesUsingScoringFunction(@RequestBody String jsonFunction, @PathVariable int skip, @PathVariable int limit, @PathVariable String keywords) { + return service.late90sMovies(skip, limit, keywords, SearchScore.of(new Document("function", Document.parse(jsonFunction)))); + } + + @GetMapping("by-genre/{genre}") + Document getMoviesWithFacets(@PathVariable String genre) { + return service.genresThroughTheDecades(genre); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/resources/application.properties b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/resources/application.properties index 8309c4461f..02ba79e336 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/main/resources/application.properties +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/main/resources/application.properties @@ -1 +1,4 @@ spring.application.name=spring-boot-persistence-mongodb-3 + +com.baeldung.atlas-search.index.query=idx-queries +com.baeldung.atlas-search.index.facet=idx-facets \ No newline at end of file diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/atlassearch/MovieAtlasSearchServiceLiveTest.java b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/atlassearch/MovieAtlasSearchServiceLiveTest.java new file mode 100644 index 0000000000..190ebc19d9 --- /dev/null +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/java/com/baeldung/boot/atlassearch/MovieAtlasSearchServiceLiveTest.java @@ -0,0 +1,67 @@ +package com.baeldung.boot.atlassearch; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.bson.Document; +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.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; + +import com.baeldung.boot.atlassearch.service.MovieAtlasSearchService; +import com.mongodb.client.model.search.SearchScore; + +@DirtiesContext +@RunWith(SpringRunner.class) +@TestPropertySource("/embedded.properties") +@SpringBootTest(classes = MongoDbAtlasSearchApplication.class) +public class MovieAtlasSearchServiceLiveTest { + + @Autowired + private MovieAtlasSearchService service; + + @Test + public void givenScoreBoost_thenFirstItemContainsPlot() { + final String plot = "space"; + + Document movies = service.late90sMovies(0, 1, plot, SearchScore.boost(2)); + + assertTrue(movies.getList("rows", Document.class) + .iterator() + .next() + .getString("fullplot") + .contains(plot)); + } + + @Test + public void givenFacetOperator_thenCorrespondingBucketsReturned() { + final String genre = "Sci-Fi"; + + Document meta = service.genresThroughTheDecades(genre); + + Long lowerBound = meta + .get("count", Document.class) + .getLong("lowerBound"); + + Document genresFacetFirstBucket = meta.get("facet", Document.class) + .get("genresFacet", Document.class) + .getList("buckets", Document.class) + .iterator() + .next(); + + Document yearFacetFirstBucket = meta.get("facet", Document.class) + .get("yearFacet", Document.class) + .getList("buckets", Document.class) + .iterator() + .next(); + + assertEquals(lowerBound, genresFacetFirstBucket.getLong("count")); + assertEquals(genre, genresFacetFirstBucket.getString("_id")); + assertNotNull(yearFacetFirstBucket); + } +} diff --git a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties index cd1c1d43c7..f47eca5023 100644 --- a/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties +++ b/persistence-modules/spring-boot-persistence-mongodb-3/src/test/resources/embedded.properties @@ -1,4 +1,4 @@ -spring.mongodb.embedded.version=4.4.9 +spring.mongodb.embedded.version=4.9.2 #spring.data.mongodb.uri=changeit #spring.data.mongodb.database=changeit