Explorando recursos de pesquisa com o Atlas Search
Aasawari Sahasrabuddhe9 min read • Published Jul 30, 2024 • Updated Aug 20, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
Bem-vindo à segunda parte de nossa série sobre como aproveitar o MongoDB Atlas Search com o Spring Boot. Na Parte 1, aprenderam como criar índices do Atlas Search usando vários métodos: o MongoDB CLI, a UI do Atlas e programaticamente dentro de um aplicativo Spring Boot. Exploramos as etapas fundamentais necessárias para configurar esses índices, fornecendo uma base sólida para implementar recursos avançados de pesquisa.
Nesta parte, vamos construir sobre essa base, mergulhando mais fundo na aplicação prática desses índices. Este artigo se concentrará na criação e execução de consultas de pesquisa usando o Spring Boot, ilustrando como aproveitar todo o potencial do Atlas Search em seus aplicativos.
Também explicaremos os principais conceitos e terminologias associados ao Atlas Search, garantindo que você tenha uma compreensão abrangente de como ele funciona.
Quer você seja um desenvolvedor habilidoso ou novo no MongoDB e no Spring Boot, esta série tem como objetivo fornecer-lhe o conhecimento e as ferramentas necessárias para implementar uma funcionalidade de pesquisa eficiente e eficaz em seus aplicativos.
- Versão Java 22
- Dados de amostra carregados no seu cluster do Atlas
Antes de escrevermos queries de pesquisa usando os índices que criamos, recomendamos que você se familiarize com os conceitos fundamentais do MongoDB Atlas Search. Um ótimo recurso para fazer isso é a parte 1 da série "Getting Started with MongoDB Atlas Search and Java".
Este tutorial fornecerá a você uma compreensão dos princípios chaves por trás do Atlas Search e demonstrará como criar diferentes tipos de índices personalizados para vários casos de uso. Ao obter esse conhecimento básico, você estará mais bem equipado para implementar e aproveitar com eficácia as funcionalidades de pesquisa em seus aplicativos Spring Boot.
Em cada uma das seções abaixo, abordaremos a terminologia e entenderemos exemplos da vida real em que os índices seriam úteis.
As queries de pesquisa mencionadas serão usadas na coleção defilmes do banco de dados sample_mflix
Se você tiver um aplicativo em que o esquema muda com frequência, a configuração da propriedade dinâmica como true criará automaticamente os índices nos campos de tipo de dados suportados . Por exemplo, o testIndex01 tem o mapeamento dinâmico para true, que indexa todos os campos com tipos de dados compatíveis .
A função abaixo usa testIndex01 para consultar todos os campos do índice de string.
1 public ArrayList<Document> searchMovies(String query) { 2 List<Document> pipeline = Arrays.asList(new Document("$search", 3 new Document("index", "testIndex01") 4 .append("text", 5 new Document("query", "cartoon") 6 .append("path", Arrays.asList("title", "plot", "fullplot")))), 7 new Document("$project", 8 new Document("_id", 0L) 9 .append("title", 1L) 10 .append("plot", 1L) 11 .append("fullplot", 1L)), 12 new Document("$limit", 3L)); 13 14 ArrayList<Document> results = new ArrayList<>(); 15 collection.aggregate(pipeline).into(results); 16 return results; 17 }
Usando a função acima, você pode encontrar filmes que mencionam a palavra- chave mencionada na _query _on nos campos title , plot e fullplot definidos no caminho. _por exemplo...
1 curl -X GET "http://localhost:8080/search?query=cartoon" | jq
...dará todos os filmes com a palavra-chave "cartoon ", conforme mostrado abaixo:
1 [ 2 { 3 "plot": "Follow Garfield behind the scenes in Cartoon World where he grows bored with his life as a comic strip star.", 4 "title": "Garfield Gets Real", 5 "fullplot": "Follow Garfield behind the scenes in Cartoon World where he grows bored with his life as a comic strip star." 6 }, 7 { 8 "plot": "A high-profiled documentary about Hergè and his major cartoon success, Tintin, based in part on previously unreleased archive tape footage.", 9 "title": "Tintin and I", 10 "fullplot": "A high-profiled documentary about Hergè and his major cartoon success, Tintin, based in part on previously unreleased archive tape footage." 11 }, 12 { 13 "plot": "Bugs Bunny reflects on his past cartoon exploits.", 14 "title": "The Bugs Bunny/Road-Runner Movie", 15 "fullplot": "The Bugs Bunny/Road Runner Movie is basically a collection of Warner Bros. short cartoon features, \"starring\" the likes of Daffy Duck, Porky Pig and Wile.E.Coyote. These animations are interspersed by Bugs Bunny reminiscing on past events and providing links between the individual animations which are otherwise unconnected. The Road Runner feature at the end consists of sketches from various RR separates (well it does in the 78-minute version anyway)." 16 } 17 ]
Nesse caso, utilizaremos o testIndex02 criado com o mapeamento de campo. Mapeamento de campo significa que o mapeamento estático foi usado e nem todos os campos são índices. Somente os campos mencionados são indexados.
Em nosso caso, o campo _genre _foi indexado e pode ser usado com o método abaixo:
1 public ArrayList<Document> searchMoviesWithGenre(String keyword){ 2 List<Document> pipeline = Arrays.asList(new Document("$search", 3 new Document("index", "testIndex02") 4 .append("text", 5 new Document("query", keyword) 6 .append("path", "genres"))), 7 new Document("$project", 8 new Document("_id", 0L) 9 .append("title", 1L) 10 .append("genres", 1L)), 11 new Document("$limit", 5L)); 12 ArrayList<Document> results = new ArrayList<>(); 13 collection.aggregate(pipeline).into(results); 14 return results; 15 }
Por exemplo, se você quiser listar todos os filmes cujo gênero é ação, pode fazer a chamada REST como:
1 curl -X GET "localhost:8080/searchMoviesWithGenre?query=action" | jq
Isso dará o resultado como:
1 [ 2 { 3 "genres": [ 4 "Action" 5 ], 6 "title": "The Perils of Pauline" 7 }, 8 { 9 "genres": [ 10 "Action" 11 ], 12 "title": "Legacy of Rage" 13 }, 14 { 15 "genres": [ 16 "Action" 17 ], 18 "title": "Batman Returns" 19 }, 20 { 21 "genres": [ 22 "Action" 23 ], 24 "title": "Dragon Inn" 25 }, 26 { 27 "genres": [ 28 "Action" 29 ], 30 "title": "Dikiy vostok" 31 } 32 ]
A partir do exemplo acima, usaremos as facetas usadas pelo métodosearchMoviesAndCategorize para realizar uma pesquisa por gêneros e categorizar os dados com base nos gêneros. Mais tarde, o testIndex02 também tem numberFacet criado no campoano, que agrupará todos os filmes lançados a cada 10 anos e retornará a contagem de todos os filmes.
1 public ArrayList<Document> searchMoviesAndCategorise(){ 2 List<Document> pipeline = Arrays.asList(new Document("$searchMeta", 3 new Document("index", "testIndex02") 4 .append("facet", 5 new Document("operator",new Document("text", 6 new Document("query", "movie").append("path", "title"))) 7 .append("facets",new Document("genresFacet", 8 new Document("type", "string").append("path", "genres")) 9 .append("yearFacet",new Document("type", "number") 10 .append("path", "year") 11 .append("boundaries", Arrays.asList(1990L, 2000L, 2010L, 2020L))))))); 12 ArrayList<Document> results = new ArrayList<>(); 13 collection.aggregate(pipeline).into(results); 14 return results; 15 }
Por exemplo, quando você usa a chamada de API abaixo, ela categoriza primeiro os dados com base nos gêneros e depois categoriza o número de filmes lançados a cada 10 anos até o momento.
1 curl -X GET "http://localhost:8080/searchMoviesAndCategorise" | jq
Isso dará o resultado como:
1 { 2 "count": { 3 "lowerBound": 125 4 }, 5 "facet": { 6 "genresFacet": { 7 "buckets": [ 8 { 9 "_id": "Comedy", 10 "count": 71 11 }, 12 { 13 "_id": "Animation", 14 "count": 42 15 }, 16 { 17 "_id": "Adventure", 18 "count": 40 19 }, 20 { 21 "_id": "Family", 22 "count": 29 23 }, 24 { 25 "_id": "Drama", 26 "count": 19 27 }, 28 { 29 "_id": "Action", 30 "count": 18 31 }, 32 { 33 "_id": "Documentary", 34 "count": 16 35 }, 36 { 37 "_id": "Horror", 38 "count": 9 39 }, 40 { 41 "_id": "Fantasy", 42 "count": 6 43 }, 44 { 45 "_id": "Crime", 46 "count": 5 47 } 48 ] 49 }, 50 "yearFacet": { 51 "buckets": [ 52 { 53 "_id": 1990, 54 "count": 19 55 }, 56 { 57 "_id": 2000, 58 "count": 62 59 }, 60 { 61 "_id": 2010, 62 "count": 27 63 } 64 ] 65 } 66 } 67 } 68 ]
O recursode preenchimento automático ajuda você a pesquisar usando palavras-chave que estão escritas de forma incompleta. Esse recurso é útil quando um usuário deseja procurar um livro com um nome incompleto no site de comércio eletrônico. Neste caso, usaremos o testIndex03 e procuraremos filmes em que foram mencionadas palavras-chave incompletas para os camposfullplot.
A função de pesquisa pode ser escrita como:
1 public ArrayList<Document> searchWithIncompleteKeyword(String keyword){ 2 List<Document> pipeline = Arrays.asList(new Document("$search", 3 new Document("index", "testIndex03") 4 .append("autocomplete", 5 new Document("query", keyword) 6 .append("path", "fullplot")) 7 .append("highlight", 8 new Document("path", "fullplot"))), 9 new Document("$project", 10 new Document("_id", 0L) 11 .append("fullplot", 1L) 12 .append("highlights", 13 new Document("$meta", "searchHighlights"))), 14 new Document("$limit", 1L)); 15 ArrayList<Document> results = new ArrayList<>(); 16 collection.aggregate(pipeline).into(results); 17 return results; 18 }
Por exemplo, se o campofullplot mencionar Spa e Cow como palavras-chave incompletas para testar Espanha, Espaço, cowboys etc., você poderá pesquisar usando a seguinte API.
1 curl -X GET "http://localhost:8080/searchMoviesWithAutocomplete?query=spa%20cow" | jq
Ele dará uma resposta como:
1 [ 2 { 3 "fullplot": "Julièn Torralba is a former movie stuntman in Almeria, Spain. He and several of his colleagues, who once made a living in American Westerns shot in Spain, now are reduced to doing stunt shows for minuscule audiences on the decaying set built for those old Westerns. Julièn wrestles with dark memories of the death of his son, also a stuntman, and with estrangement from his daughter-in-law Laura and her son Carlos. Carlos, a young boy, becomes intrigued with his late father's life and runs away to join Julièn and his band of has-beens. There Carlos is initiated into the rambunctious life of these hard-drinking faux cowboys. But when Laura, a powerful executive looking for a new site for a tourist resort, learns that Carlos has joined the hated Julièn, she moves to destroy even this remnant of Julièn's once-proud career. Julièn and the cowboys decide to fight back the only way they know how.", 4 "highlights": [ 5 { 6 "score": 2.126451015472412, 7 "path": "fullplot", 8 "texts": [ 9 { 10 "value": "Julièn Torralba is a former movie stuntman in Almeria, ", 11 "type": "text" 12 }, 13 { 14 "value": "Spain. He and", 15 "type": "hit" 16 } 17 ] 18 }, 19 { 20 "score": 1.518623948097229, 21 "path": "fullplot", 22 "texts": [ 23 { 24 "value": "He and several of his colleagues, who once made a living in American Westerns shot in ", 25 "type": "text" 26 }, 27 { 28 "value": "Spain, now are", 29 "type": "hit" 30 }, 31 { 32 "value": " reduced to doing stunt shows for minuscule audiences on the decaying set built for those old Westerns. ", 33 "type": "text" 34 } 35 ] 36 }, 37 { 38 "score": 1.869809865951538, 39 "path": "fullplot", 40 "texts": [ 41 { 42 "value": "There Carlos is initiated into the rambunctious life of these hard-drinking faux ", 43 "type": "text" 44 }, 45 { 46 "value": "cowboys. But when", 47 "type": "hit" 48 } 49 ] 50 }, 51 { 52 "score": 1.9461908340454102, 53 "path": "fullplot", 54 "texts": [ 55 { 56 "value": "Julièn and the ", 57 "type": "text" 58 }, 59 { 60 "value": "cowboys decide to", 61 "type": "hit" 62 }, 63 { 64 "value": " fight back the only way they know how.", 65 "type": "text" 66 } 67 ] 68 } 69 ] 70 } 71 ]
Às vezes, pesquisamos itens/produtos em sites usando a grafia errada devido à correção automática ou ao desconhecimento da grafia correta, mas o aplicativo ainda nos fornece os resultados corretos. Isso é possível devido ao recurso de pesquisa difusa.
O Atlas Search do MongoDB também permite que você pesquise o texto com a ortografia incorreta.
1 public ArrayList<Document> searchWithMisspelledTitle(String keyword){ 2 List<Document> result = Arrays.asList(new Document("$search", 3 new Document("index", "testIndex03") 4 .append("text", 5 new Document("query", keyword) 6 .append("path", "title") 7 .append("fuzzy", 8 new Document("maxEdits", 2L) 9 .append("maxExpansions", 100L)))), 10 new Document("$project", 11 new Document("title", 1L) 12 .append("cast", 1L)), 13 new Document("$limit", 3L)); 14 return collection.aggregate(result).into(new ArrayList<>()); 15 }
Nesse caso, você pode pesquisar o filme com um título escrito incorretamente, e o nome correto do filme aparecerá nos resultados.
Por exemplo, se você pesquisar o filme com a chamada REST abaixo...
1 curl -X GET "localhost:8080/searchMoviesWithIncorrectSpelling?query=Gerti%20the%20Dinosor" | jq
... quando a palavra Dinosour for escrita erroneamente como Dinosor, ela fornecerá os resultados como :
1 [ 2 { 3 "_id": { 4 "timestamp": 1463423888, 5 "date": "2016-05-16T18:38:08.000+00:00" 6 }, 7 "cast": [ 8 "Winsor McCay", 9 "George McManus", 10 "Roy L. McCardell" 11 ], 12 "title": "Gertie the Dinosaur" 13 }, 14 { 15 "_id": { 16 "timestamp": 1463423899, 17 "date": "2016-05-16T18:38:19.000+00:00" 18 }, 19 "title": "Dinosaur", 20 "cast": [ 21 "D.B. Sweeney", 22 "Alfre Woodard", 23 "Ossie Davis", 24 "Max Casella" 25 ] 26 }, 27 { 28 "_id": { 29 "timestamp": 1463423974, 30 "date": "2016-05-16T18:39:34.000+00:00" 31 }, 32 "cast": [ 33 "Stan Adelstein", 34 "Lanice Archer", 35 "Robert Bakker", 36 "Philip Currie" 37 ], 38 "title": "Dinosaur 13" 39 } 40 ]
Semelhante ao caso mencionado acima, o MongoDB Atlas Search também permite a pesquisa com palavras-chave sinônimas, ou seja, palavras que têm o mesmo significado ou quase o mesmo significado.
Quando criamos o índice de pesquisa, especificamos a coleção como Coleção de origem dos sinônimos, de onde os sinônimos serão mapeados. O testIndex04 menciona o nome da collection como test_synonyms, que é a collection de origem.
A query de pesquisa é escrita como:
1 public ArrayList<Document> searchWithSynonyms(String keyword){ 2 List<Document> result = Arrays.asList(new Document("$search", 3 new Document("index", "testIndex04") 4 .append("text", 5 new Document("path", "fullplot") 6 .append("query", keyword) 7 .append("synonyms", "synonymName"))), 8 new Document("$limit", 10L), 9 new Document("$project", 10 new Document("_id", 0L) 11 .append("title", 1L) 12 .append("fullplot", 1L) 13 .append("score", 14 new Document("$meta", "searchScore"))), 15 new Document("$limit", 3L)); 16 return collection.aggregate(result).into(new ArrayList<>()); 17 }
O $meta usado na query com searchScore determinará a proximidade do documento da palavra-chave pesquisada. Antes de fazermos o restante da chamada para testar a query, precisamos criar o mapeamento para os sinônimos em uma collection diferente.
Insira os dados abaixo na coleção test_synonyms.
1 [ 2 { 3 mappingType: 'explicit', 4 input: [ 'love', 'romance' ], 5 synonyms: [ 'love', 'romance' ] 6 }, 7 { 8 mappingType: 'equivalent', 9 synonyms: [ 'car', 'vehicle', 'automobile' ] 10 } 11 ]
Para testar a query acima, você pode usar a chamada REST como:
1 curl -X GET "localhost:8080/searchMoviesWithSynonyms?query=love" | jq
Isso fornecerá resultados com base no mapeamento criado, conforme mostrado abaixo.
1 [ 2 { 3 "title": "Oh, Woe Is Me", 4 "fullplot": "Romance about Simon Donnadieu and his decision to leave his ever-loving wife Rachel.", 5 "score": 3.9741177558898926 6 }, 7 { 8 "title": "27 Missing Kisses", 9 "fullplot": "The summerly adventures of teen Sybilla, who falls in love with a middle-aged father, while being romanced by the his teen son.", 10 "score": 3.8209657669067383 11 }, 12 { 13 "title": "The End of the Affair", 14 "fullplot": "In wartorn London Maurice Bendrix falls in love with neighbor Sarah Miles. They begin an illicit romance behind Sarah's husband's back. While war does not last forever, neither does love in this existentialist tale.", 15 "score": 3.753319263458252 16 } 17 ]
O código completo para o aplicativo usando todos os casos mencionados acima está disponível no repositório do GitHub.
Depois de utilizar todos os índices criados, você pode usar o método abaixo para excluir todos os índices de pesquisa que foram criados por meio do aplicativo.
1 public void deleteSearchIndexes(String dbName, String collectionName) { 2 MongoDatabase database = mongoClient.getDatabase(dbName); 3 MongoCollection<Document> collection = database.getCollection(collectionName); 4 5 List<String> indexNames = new ArrayList<>(); 6 for (Document indexInfo : collection.listSearchIndexes()) { 7 String indexName = indexInfo.getString("name"); 8 if (!"_id_".equals(indexName)) { 9 indexNames.add(indexName); 10 } 11 } 12 // Drop each index using its name from the array 13 for (String indexName : indexNames) { 14 collection.dropSearchIndex(indexName); 15 } 16 System.out.println("Deleted all indexes created"); 17 }
E chame a API com o comando abaixo
1 curl -X DELETE "localhost:8080/deleteSearchIndexes?dbName=<dbName>&collectionName=<collectionName>"
Nesta segunda parte de nossa série sobre como explorar o MongoDB Atlas Search com o Spring Boot, analisamos as aplicações práticas dos índices de pesquisa que criamos na Parte 1. Ao demonstrar vários cenários de pesquisa, destacamos a versatilidade e o poder do Atlas Search em lidar com diferentes tipos de consultas e estruturas de dados.
Nesta parte, abordamos alguns casos de uso como autocompletar, fuzzy, mapeamento de campo etc. Se quiser saber mais sobre outros casos, também temos outros artigos - MongoDB Atlas Search Using the Java Driver and Spring Data e How to Build a Search Service in Java - nos quais você pode explorar mais casos de uso.
Se você tiver dúvidas ou sugestões, fique à vontade para entrar em contato no MongoDB Community e também para explorar mais tutoriais interessantes por meio do nosso MongoDB Developer Center.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
{Parte de uma série