Usar o Atlas Search do Java
Erik Hatcher13 min read • Published Jul 14, 2023 • Updated Jul 14, 2023
APLICATIVO COMPLETO
Avalie esse Artigo
Caro colega desenvolvedor, seja bem-vindo!
OAtlas Search é um mecanismo de pesquisa de texto completo incorporado no MongoDB Atlas que oferece uma experiência perfeita e escalável para criar funcionalidades de aplicativos baseados em relevância. Construído no Apache Lucene, o Atlas Search elimina a necessidade de executar um sistema de busca separado em conjunto com seu banco de dados. A porta de entrada para o Atlas Search é o estágio do pipeline de agregação
$search
.O estágio$search, como um dos mais recentes membros da família de pipeline de agregação MongoDB, obteve suporte nativo e conveniente adicionado a vários drivers de idioma. O suporte ao driver ajuda os desenvolvedores a criar códigos concisos e legíveis. Este artigo explica como usar o suporte do Atlas Search integrado ao driver Java do MongoDB, onde veremos como usar o driver e como lidar com
$search
recursos que ainda não têm métodos de conveniência do driver nativo ou foram lançados após o driver foi lançado, e um pouco da pontuação de relevância do Atlas Search. Vamos começar!A pesquisa de texto completo é um conjunto enganosamente sofisticado de conceitos e tecnologias. Do ponto de vista do usuário, é simples: o bom e velho
?q=query
no URL de seus aplicativos da Web e os documentos relevantes são retornados magicamente. Há muito por trás da caixa de pesquisa clássica da lupa, desde analisadores, sinônimos, operadores difusos e facetas até preenchimento automático, ajuste de relevância e muito mais. Sabemos que é muito para digerir. O Atlas Search trabalha duro para tornar as coisas cada vez mais fáceis para os desenvolvedores, então tenha certeza de que você está no lugar mais confortável para começar sua jornada nas alegrias e no poder da pesquisa de texto completo. É certo que encobrimos os detalhes aqui neste artigo, para que você comece a trabalhar com algo imediatamente compreensível e útil para vocês, colegas desenvolvedores Java. Seguindo o exemplo básico fornecido aqui, você terá a estrutura para experimentar e aprender mais sobre os detalhes omitidos.Precisamos de duas coisas para começar, um banco de dados e dados. Protegemos você com ambos. Primeiro, comece fazendo login na sua conta Atlas. Se você ainda não tiver uma conta do Atlas, siga as etapas para a UI do Atlas no tutorial Get Started with Atlas“ .
Se você já tinha uma conta do Atlas ou talvez, como eu, tenha lido o tutorial muito rapidamente e ignorado a etapa de adicionar seu endereço IP à lista de endereços IP confiáveis, cuide disso agora. O Atlas só permite o acesso aos endereços IP e usuários que você configurou, mas é restrito de outra forma.
Agora que você está conectado à sua conta do Atlas, adicione os conjuntos de dados de exemplo ao seu ambiente. Especificamente, estamos usando a collection sample_mflix aqui. Após adicionar os dados de amostra, ative o Atlas Search para essa collection navegando até a seção Pesquisar na visualização Bancos de dados e clicando em "Create Search Index. "
Uma vez no assistente "Create Index ", use o Editor Visual, escolha a collection sample_mflix.movies, deixe o nome do índice como "default " e, finalmente, clique em "Create Search Index. "
A criação do índice de pesquisa levará alguns minutos e, após isso, uma notificação por e-mail será enviada. O status de processamento de indexação também pode ser rastreado na UI.
Veja como a seção de pesquisa deve ter a seguinte aparência para você:
Voilá, agora você indexou os dados do filme no Atlas Search e pode realizar query de texto completo sofisticadas nele. Go em frente e experimente usar o prático Testador de Pesquisa, clicando no botão "Query ". Tente digitar alguns de seus títulos de filmes favoritos ou nomes de atores, ou até mesmo palavras que apareceriam no enredo ou gênero.
Atrás dos palcos do Search Tester espreita o estágio do pipeline $search. Clicar em “Edit $search Query” expõe todo o estágio $search em toda a sua glória JSON, permitindo que você experimente a sintaxe e o comportamento.
Esta é a nossa primeira amostra da sintaxe $search. O prático botão "copy " (o canto superior direito do painel lateral do editor de código) copia o código para a área de transferência para que você possa colá-lo em suas ferramentas favoritas de pipeline de agregação do MongoDB , como MongoDB Compass, MongoDB shellou a ferramenta de agregação de UI Atlas ( mostrado abaixo). Há um link "aggregation pipeline " que conectará você diretamente à ferramenta de agregação na collection atual.
Neste ponto, seu ambiente está configurado e sua coleção pode ser pesquisada pelo Atlas. Agora é hora de programar!
Vamos primeiro parar um momento para refletir e avaliar o que aconteceu nos backstage de nossos cliques de assistente até este ponto:
- Um cluster MongoDB gerenciado, escalável e confiável acabou de girar.
- Muitas coletas de dados de amostra foram ingeridas, incluindo o banco de dados de filmes usado aqui.
- Um índice de texto completo, flexível e com replicação tripla foi configurado e criado a partir do conteúdo existente e permanece sincronizado com as alterações do banco de dados.
Por meio da UI do Atlas e de outras ferramentas, como o MongoDB Compass, agora podemos consultar nossa coleção de filmes de, é claro, todas as formas usuais do MongoDB e também por meio de um índice de texto completo testado e eficiente com resultados classificados por relevância. Agora cabe a nós, outros desenvolvedores, leva-lo além da linha de chegada e construir os aplicativos que permitirão e facilitarão que os documentos mais úteis ou interessantes sejam filtrados para o topo. E, nesse caso, estamos em uma tarefa para construir código Java para pesquisar nosso índice de Atlas Search.
Vamos responder a esta pergunta com dados de nossos filmes:
Quais filmes românticos e de drama tiveram a participação de Keanu Reeves?
Sim, poderíamos responder a essa pergunta específica sabendo o caso exato e a ortografia de cada valor de campo de forma direta, usando esse pipeline de agregação:
1 [ 2 { 3 $match: { 4 cast: { 5 $in: ["Keanu Reeves"], 6 }, 7 genres: { 8 $all: ["Drama", "Romance"], 9 }, 10 }, 11 } 12 ]
Vamos supor que temos uma interface de usuário que permite ao usuário selecionar um ou mais gêneros para filtrar e uma caixa de texto para digitar uma consulta de formulário livre (consulte os recursos no final para ver um site como esse). Se o usuário tivesse digitado "keanu reeves", todas as minúsculas, o $match acima não encontraria nenhum filme. Fazer correspondência de valores exatos e conhecidos é uma capacidade importante e necessária, com certeza, mas ao apresentar interfaces de consulta de formato livre para humanos, precisamos permitir erros de digitação, insensibilidade a maiúsculas e minúsculas, erros de transcrição de voz e outras consultas inexatas e confusas.
Usando o índice do Atlas Search que já configuramos, agora podemos lidar facilmente com uma variedade de consultas de texto completo. Vamos nos limitar a este exemplo por toda parte para que você possa comparar e contrastar as queries $match padrão com as queries $search sofisticadas.
Por fim, independentemente da linguagem de codificação, do ambiente ou do driver que usamos, uma representação BSON de nossa solicitação de pipeline de agregação é tratada pelo servidor. A visualização Agregação na UI do Atlas e de forma muito semelhante no Compass, nossa útil UI do lado do cliente do MongoDB para consulta e análise de dados do MongoDB, pode ajudar a orientá-lo pela sintaxe, com links diretamente para a documentação pertinente do aggregation pipeline do Atlas Search.
Em vez de aumentar gradualmente até nosso exemplo final, aqui está o pipeline de agregação completo para que você o tenha disponível à medida que o adaptamos ao código Java. Esse canal de agregação realiza uma consulta de pesquisa, filtrando os resultados de filmes categorizados como gêneros de drama e romance, que têm “keanu reeves” no campo do elenco, retornando apenas alguns campos dos primeiros documentos 10 mais bem classificados.
1 [ 2 { 3 "$search": { 4 "compound": { 5 "filter": [ 6 { 7 "compound": { 8 "must": [ 9 { 10 "text": { 11 "query": "Drama", 12 "path": "genres" 13 } 14 }, 15 { 16 "text": { 17 "query": "Romance", 18 "path": "genres" 19 } 20 } 21 ] 22 } 23 } 24 ], 25 "must": [ 26 { 27 "phrase": { 28 "query": "keanu reeves", 29 "path": { 30 "value": "cast" 31 } 32 } 33 } 34 ] 35 }, 36 "scoreDetails": true 37 } 38 }, 39 { 40 "$project": { 41 "_id": 0, 42 "title": 1, 43 "cast": 1, 44 "genres": 1, 45 "score": { 46 "$meta": "searchScore" 47 }, 48 "scoreDetails": { 49 "$meta": "searchScoreDetails" 50 } 51 } 52 }, 53 { 54 "$limit": 10 55 } 56 ]
Neste ponto, Go e copie o pipeline de agregação JSON acima e cole-o na UI do Atlas ou no Compass. Há um recurso interessante (a alternância de modo "< /> TEXT" ) em que você pode colar todo o JSON que acabou de copiar. Veja como os resultados devem ficar para você:
À medida que adaptamos o pipeline de agregação de três estágios para Java, explicaremos as coisas com mais detalhes.
Gastamos tempo aqui enfatizando essa estrutura semelhante a JSON porque ela nos ajudará em nossa codificação Java. Também será útil poder trabalhar com essa sintaxe em ferramentas ad hoc, como o Compass, para experimentar várias combinações de opções e estágios para chegar ao que melhor serve nossos aplicativos e traduzir esse pipeline de agregação em código Java. É também a linguagem de query/sintaxe de query mais comumente documentada para MongoDB e Atlas Search; é importante ser habilidoso com ele.
Versão 4.7 do driver Java do MongoDB foi lançado em julho do ano passado (2022), adicionando métodos de conveniência para o estágio Atlas
$search
, enquanto o Atlas Search foi disponibilizado ao público em geral dois anos antes. Nesse período, os desenvolvedores Java não estavam sem sorte, pois a API direta do documento BSON chama a construção de um estágio $search. Os exemplos de código nesse período usaram new Document("$search",...)
. Este artigo mostra uma maneira mais fácil de nós, desenvolvedores Java, usarmos o estágio$search
, permitindo que parâmetros com nomes claros e fortemente digitados orientem você. O método e o preenchimento automático de parâmetros do seu IDE economizarão tempo para criar um código mais legível e confiável.Você precisará de uma versão moderna do Java, algo como:
1 $ java --version 2 openjdk 17.0.7 2023-04-18 3 OpenJDK Runtime Environment Homebrew (build 17.0.7+0) 4 OpenJDK 64-Bit Server VM Homebrew (build 17.0.7+0, mixed mode, sharing)
Agora pegue o código do nosso repositório usando
git clone
e vá para o diretório de trabalho:1 git clone https://github.com/mongodb-developer/getting-started-search-java 2 cd getting-started-search-java
Depois de clonar esse código, copie a string de conexão da interface do usuário do Atlas (o botão “Connect” na página do banco de dados). Você usará essa connection string em um momento para executar o código que se conecta ao seu cluster.
Agora, abra um prompt de linha de comando no diretório em que você colocou o código e execute:
1 ATLAS_URI="<<insert your connection string here>>" ./gradlew run
Certifique-se de preencher o nome de usuário e senha apropriados na connection string. Se você ainda não tiver o Gradle instalado, o comando
gradlew
deverá instalá-lo na primeira vez que for executado. Neste ponto, você deve obter algumas páginas de saída para o seu console. Se o processo travar por alguns segundos e depois atingir o tempo limite com uma mensagem de erro, verifique as permissões de rede do Atlas, a connection string que você especificou a configuraçãoATLAS_URI
, incluindo o nome de usuário e a senha.Usar o comando
run
do Gradle é uma maneira conveniente de executar o Java main()
do nosso FirstSearchExample
. Ele também pode ser executado de outras maneiras, como por meio de um IDE. Apenas certifique-se de definir a variável de ambienteATLAS_URI
para o ambiente que executa o código.Idealmente, neste ponto, o código foi executado com êxito, executando a query de pesquisa que descrevemos, imprimindo estes resultados:
1 Sweet November 2 Cast: [Keanu Reeves, Charlize Theron, Jason Isaacs, Greg Germann] 3 Genres: [Drama, Romance] 4 Score:6.011996746063232 5 6 Something's Gotta Give 7 Cast: [Jack Nicholson, Diane Keaton, Keanu Reeves, Frances McDormand] 8 Genres: [Comedy, Drama, Romance] 9 Score:6.011996746063232 10 11 A Walk in the Clouds 12 Cast: [Keanu Reeves, Aitana Sènchez-Gijèn, Anthony Quinn, Giancarlo Giannini] 13 Genres: [Drama, Romance] 14 Score:5.7239227294921875 15 16 The Lake House 17 Cast: [Keanu Reeves, Sandra Bullock, Christopher Plummer, Ebon Moss-Bachrach] 18 Genres: [Drama, Fantasy, Romance] 19 Score:5.7239227294921875
Portanto, há quatro filmes que correspondem aos nossos critérios - nossa missão inicial foi cumprida.
Vamos agora analisar nosso projeto e código, apontando as partes importantes que você usará em seu próprio projeto. Primeiro, nosso arquivo
build.gradle
especifica que nosso projeto depende do driver Java do MongoDB, até a versão específica do driver. Há também um plug-inapplication
conveniente para que possamos usar o alvorun
como acabamos de fazer.1 plugins { 2 id 'java' 3 id 'application' 4 } 5 6 group 'com.mongodb.atlas' 7 version '1.0-SNAPSHOT' 8 9 repositories { 10 mavenCentral() 11 } 12 13 dependencies { 14 implementation 'org.mongodb:mongodb-driver-sync:4.10.1' 15 implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.17.1' 16 } 17 18 application { 19 mainClass = 'com.mongodb.atlas.FirstSearchExample' 20 }
Consulte Docs para obter mais detalhes sobre como adicionar o driver Java do MongoDB ao seu projeto.
Na estrutura típica do projeto Gradle, nosso código Java reside em
src/main/java/com/mongodb/atlas/
em FirstSearchExample.java.Vamos percorrer este código, seção por seção, em um pouco de ordem inversa. Primeiro, abrimos uma conexão com nossa collection, extraindo a string de conexão da variável de ambientedo
ATLAS_URI
:1 // Set ATLAS_URI in your environment 2 String uri = System.getenv("ATLAS_URI"); 3 if (uri == null) { 4 throw new Exception("ATLAS_URI must be specified"); 5 } 6 7 MongoClient mongoClient = MongoClients.create(uri); 8 MongoDatabase database = mongoClient.getDatabase("sample_mflix"); 9 MongoCollection<Document> collection = database.getCollection("movies");
Nosso objetivo final é chamar
collection.aggregate()
com nossa lista de estágios do pipeline: pesquisa, projeto e limite. Existem métodos de conveniência do driver em com.mongodb.client.model.Aggregates
para cada um deles.1 AggregateIterable<Document> aggregationResults = collection.aggregate(Arrays.asList( 2 searchStage, 3 project(fields(excludeId(), 4 include("title", "cast", "genres"), 5 metaSearchScore("score"), 6 meta("scoreDetails", "searchScoreDetails"))), 7 limit(10)));
Os estágios
$project
e $limit
são especificados totalmente in-line acima. Definiremos searchStage
em um momento. O estágioproject
usa metaSearchScore
, um método de conveniência do driver Java, para mapear a pontuação computada do Atlas Search (mais sobre isso abaixo) para um pseudocampo chamado score
. Além disso, o Atlas Search pode fornecer as explicações de pontuação, que em si é um sucesso de desempenho a ser gerado, portanto, usado apenas para depuração e experimentação. Os detalhes de explicação da pontuação devem ser solicitados como uma opção no estágiosearch
para que estejam disponíveis para projeção aqui. Não há um método de conveniência para projetar explicações de pontuação, portanto, usamos o método genérico meta()
para fornecer o nome do pseudocampo e a chave do valor meta que o Atlas Search retorna para cada documento. O código Java acima gera o seguinte pipeline de agregação, que havemos feito anteriormente manualmente acima, mostrando-o aqui para mostrar o código Java e as partes de pipeline de agregação geradas correspondentes.1 [ 2 { 3 "$search": { ... } 4 }, 5 { 6 "$project": { 7 "_id": 0, 8 "title": 1, 9 "cast": 1, 10 "genres": 1, 11 "score": { 12 "$meta": "searchScore" 13 }, 14 "scoreDetails": { 15 "$meta": "searchScoreDetails" 16 } 17 } 18 }, 19 { 20 "$limit": 10 21 } 22 ]
O
searchStage
consiste em um operador de pesquisa e uma opção adicional. Queremos os detalhes da explicação da pontuação de relevância de cada documento gerado e retornado, o que é ativado pela configuração scoreDetails
que foi desenvolvida e lançada após o lançamento da versão do driver Java. Felizmente, a equipe do driver Java incorporou recursos de passagem para poder definir opções arbitrárias além das incorporadas, a fim de prepará-lo para o futuro.SearchOptions.searchOptions().option()
nos permite definir a opção scoreDetails
no estágio$search
como verdadeira. Reiterando a observação anterior, a geração de detalhes de pontuação é um problema de desempenho no Lucene, portanto, ative essa configuração apenas para depuração ou experimentação durante a inspeção, mas não a ative em ambientes sensíveis ao desempenho.1 Bson searchStage = search( 2 compound() 3 .filter(List.of(genresClause)) 4 .must(List.of(SearchOperator.of(searchQuery))), 5 searchOptions().option("scoreDetails", true) 6 );
Esse código cria essa estrutura:
1 "$search": { 2 "compound": { 3 "filter": [ . . . ], 4 "must": [ . . . ] 5 }, 6 "scoreDetails": true 7 }
Deixamos algumas variáveis para preencher:
filters
e searchQuery
.O que são filtros em comparação com outras cláusulas do operador composto?
filter
Cláusulas : para restringir o escopo da consulta, sem afetar a pontuação de relevância resultantemust
: cláusulas de query necessárias que afetam as pontuações de relevânciashould
: cláusulas de query opcionais, afetando as pontuações de relevânciamustNot
: cláusulas que não devem corresponder
Nosso filtro (sem pontuação) é uma cláusula de operador de pesquisa única que combina os critérios necessários para os gêneros Drama e Romance:
1 SearchOperator genresClause = SearchOperator.compound() 2 .must(Arrays.asList( 3 SearchOperator.text(fieldPath("genres"),"Drama"), 4 SearchOperator.text(fieldPath("genres"), "Romance") 5 ));
E esse código cria essa estrutura do operador de query:
1 "compound": { 2 "must": [ 3 { 4 "text": { 5 "query": "Drama", 6 "path": "genres" 7 } 8 }, 9 { 10 "text": { 11 "query": "Romance", 12 "path": "genres" 13 } 14 } 15 ] 16 }
Observe como aninhamos
genresClause
em nosso arrayfilter
, que recebe uma lista de SearchOperator
s. SearchOperator
é uma classe de driver Java com métodos construtores convenientes para alguns, mas não todos, os operadores de pesquisa disponíveis do Atlas Search. Você pode ver que usamos SearchOperator.text()
para construir as cláusulas dos gêneros.Por último, mas não menos importante, está a primária (pontuação!) Cláusula do operador de pesquisa
phrase
para pesquisar “keanu reeves” no campocast
. Infelizmente, este é um operador de pesquisa que atualmente não possui suporte integrado a SearchOperator
. Mais uma vez, parabéns à equipe de desenvolvimento do driver Java por criar uma passagem para objetos BSON arbitrários, desde que conheçamos a sintaxe JSON correta. Usando SearchOperator.of()
, criamos um operador arbitrário a partir de um documento BSON. Observação: é por isso que foi enfatizado desde o início para se tornar mais experiente com a estrutura JSON da sintaxe do pipeline de agregação.1 Document searchQuery = new Document("phrase", 2 new Document("query", "keanu reeves") 3 .append("path", "cast"));
Então agora criamos o pipeline de agregação. Para mostrar os resultados (mostrado anteriormente), simplesmente iteramos por
aggregationResults
:1 aggregationResults.forEach(doc -> { 2 System.out.println(doc.get("title")); 3 System.out.println(" Cast: " + doc.get("cast")); 4 System.out.println(" Genres: " + doc.get("genres")); 5 System.out.println(" Score:" + doc.get("score")); 6 // printScoreDetails(2, doc.toBsonDocument().getDocument("scoreDetails")); 7 System.out.println(""); 8 });
Os resultados são ordenados em ordem decrescente de pontuação. A pontuação é um fator numérico baseado na relação entre a consulta e cada documento. Nesse caso, o único componente de pontuação da nossa consulta foi uma consulta por frase “keanu reeves”. Curiosamente, nossos resultados têm documentos com pontuações diferentes! Por que é isso? Se abordássemos tudo, este artigo nunca terminaria, portanto, abordar as diferenças de pontuação está além desse escopo, mas explicaremos um pouco abaixo para obter bônus e material futuro.
Agora você é um desenvolvedor Java habilidoso do Atlas Search — muito bem! Você está no caminho certo para aprimorar seus aplicativos com o poder da pesquisa de texto completo. Com apenas as etapas e o código apresentados aqui, mesmo sem configuração adicional e compreensão mais profunda da pesquisa, o poder da pesquisa está disponível para você.
Isso é apenas o começo. E é importante, à medida que refinamos nosso aplicativo para atender às crescentes necessidades de relevância de nossos usuários, continuar a maneira de aprender do Atlas Search.
Terminamos nosso código com alguns resultados de diagnóstico perspicazes. Uma execução de pipeline de agregação pode ser explicada,despejando detalhes de planos de execução e horários de desempenho. Além disso, o processo do Atlas Search,
mongot
, fornece detalhes da interpretação e estatísticas do estágio$search
.1 System.out.println("Explain:"); 2 System.out.println(format(aggregationResults.explain().toBsonDocument()));
Deixaremos o aprofundamento desses detalhes como um exercício para o leitor, observando que você pode aprender muito sobre como as consultas são interpretadas/analisadas estudando a saída explain().
A relevância da pesquisa é uma arte científica. Sem entrar em equações matemáticas e descrições detalhadas da pesquisa de recuperação de informações, vamos nos concentrar na situação concreta de pontuação apresentada em nosso aplicativo aqui. O componente de pontuação da nossa consulta é uma consulta de frase de "keanu reeves" no campo de elenco. Fazemos uma consulta
phrase
em vez de uma consultatext
para que possamos pesquisar essas duas palavras de forma contígua, em vez de "keanu OR reeves" ("keanu" é um termo raro, é claro, mas há muitos "reeves").A pontuação leva em conta o comprimento do campo (o número de termos/palavras no conteúdo), entre outros fatores. Por baixo, durante a indexação, cada valor do campo de elenco é executado por meio de um processo de análise que tokeniza o texto. A tokenização é um processo que divide o conteúdo em unidades pesquisáveis, chamadas de termos. Um "term" pode ser uma palavra ou fragmento de uma palavra, ou o texto exato, dependendo das configurações do analisador. Dê uma olhada nos valores do campo
cast
nos filmes retornados. Usando o analisador padrãolucene.standard
, os tokens emitidos se dividem em espaços em branco e em outros limites de palavras, como o caractere de traço.Agora você vê como o comprimento do campo (número de termos) varia entre os documentos? Se você estiver interessado nos detalhes ainda mais obsoletos de como a Lucene executa a pontuação de nossa query, descomente o código
printScoreDetails
em nosso loop de saída de resultados.Não se preocupe se esta seção for um pouco demais para ser aceita agora. Fique ligado — em breve lançaremos algum conteúdo de explicação de pontuação.
Poderemos corrigir rapidamente a ordem para, pelo menos, não enviesar com base na ausência de nomes de atores hifenizados. Mover queryClause para a seção
filters
, em vez da seçãomust
, de modo que não haja cláusulas de pontuação, apenas cláusulas de filtragem, deixará todos os documentos com a mesma classificação.Há muitos recursos úteis do Atlas Search disponíveis, vários com link embutido acima; recomendamos que você clique neles para se afundar mais. Estas três etapas rápidas ajudarão você a pesquisar rapidamente:
E, finalmente, deixaremos você com a slick demonstration of Atlas Search na collection de filmes em https://www.atlassearchmovies.com/ (embora observe que ele pesquisa de forma difusa todos os campos de texto pesquisáveis, não apenas o campo de elenco, e faz o mesmo com a query de lógica OR, que é diferente da query
phrase
apenas no campo cast
que executamos aqui).