BAEL 6701 - MongoDB - Atlas Search using the Java Driver and Spring Data (#14777)

* research 1

* new search methods

* first draft

* removing old code

* updated to parent-boot-3
This commit is contained in:
Ulisses Lima 2023-09-30 03:44:07 -03:00 committed by GitHub
parent cafabdfe55
commit a93751923d
8 changed files with 342 additions and 31 deletions

View File

@ -9,27 +9,12 @@
<parent>
<groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId>
<artifactId>parent-boot-3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../parent-boot-2</relativePath>
<relativePath>../../parent-boot-3</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>${mongodb-driver.version}</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>${mongodb-driver.version}</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
<version>${mongodb-driver.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -37,16 +22,6 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<exclusions>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
</exclusion>
<exclusion>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
@ -57,12 +32,13 @@
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
<version>${de.flapdoodle.embed.mongo.version}</version>
</dependency>
</dependencies>
<properties>
<mongodb-crypt.version>1.7.3</mongodb-crypt.version>
<mongodb-driver.version>4.9.1</mongodb-driver.version>
<start-class>com.baeldung.boot.atlassearch.MongoDbAtlasSearchApplication</start-class>
<mongodb-crypt.version>1.8.0</mongodb-crypt.version>
<de.flapdoodle.embed.mongo.version>4.9.2</de.flapdoodle.embed.mongo.version>
</properties>
</project>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<Document> collection;
public MovieAtlasSearchService(MongoTemplate mongoTemplate) {
MongoDatabase database = mongoTemplate.getDb();
this.collection = database.getCollection("movies");
}
public static void debug(List<Bson> 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<Bson> 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<Bson> 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<Document> moviesByKeywords(String keywords) {
List<Bson> 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<Document>());
}
public Document genresThroughTheDecades(String genre) {
List<Bson> 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();
}
}

View File

@ -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<Document> 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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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