Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
MongoDBchevron-right

Dados da primavera desbloqueados: queries avançadas com MongoDB

Ricardo Mello7 min read • Published Nov 08, 2024 • Updated Nov 08, 2024
SpringMongoDBFramework de agregaçãoJava
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Aposto que você está aqui porque já leu o primeiro artigo desta série, Começando com Java e MongoDB. Mas, mesmo que não tenha feito isso, não se incomode — você está no lugar certo. Na primeira parte desta série, exploramos os principais conceitos relacionados ao Spring e aprendermos como criar um projeto e configurá-lo para integração perfeita com o MongoDB. Esta é a segunda parte da série Spring Data Unlocked, em que continuaremos trabalhando em nosso projeto e exploraremos os recursos do Spring Data criando queries avançadas e vendo como isso pode ser feito sem problemas.

Pré-requisitos

Atualizando nossa memória

Se você não se lembra do que conversamos antes, não se desespere. Leia o artigo anterior sobre como começar a usar Java e MongoDB ou atualize sua memória com nosso modelo de dados no qual estamos trabalhando:
1{
2 "id": "672182814338f60133ee26e1",
3 "transactionType": "Debit",
4 "amount": 888.0,
5 "currency": "USD",
6 "status": "In Progress",
7 "description": "Transfer to Ricardo",
8 "createdAt": "2024-10-09T14:00:00",
9 "accountDetails": {
10 "originator": {
11 "accountNumber": "2376543213",
12 "name": "Maria",
13 "bank": "Bank G"
14 },
15 "beneficiary": {
16 "accountNumber": "2234987651",
17 "name": "Ricardo Mello",
18 "bank": "Bank V"
19 }
20 }
21}
Nosso modelo de transação contém essa estrutura e prosseguiremos com ela.

O MongoRepository

Ainda com foco na produtividade e na velocidade, continuaremos usando nossa MongoRepository interface e criaremos uma query para recuperar nossas transações por tipo. Abra a TransactionRepository classe e adicione o seguinte método:
1List<Transaction> findByTransactionType(String type);
Como você pode ver, estamos encontrando todas as transações por tipo. O Spring, por meio de convenções de nomenclatura (queries derivadas), permite criar queries com base nos nomes dos métodos.
Dica: podemos monitorar o processo de registro ativando uma configuração DEBUG no application.properties arquivo:
1logging.level.org.springframework.data.mongodb=DEBUG
Poderemos ver a query no console:
12024-10-15T18:30:33.855-03:00 DEBUG 28992
2[SpringShop] [nio-8080-exec-6] o.s.data.mongodb.core.MongoTemplate:
3find using query: { "transactionType" : "Transfer"} fields: Document{{}} sort: { "transactionType" : "Transfer"} for class: class com.mongodb.Transaction in collection: transactions
Agora, digamos que queremos todas as transações cujo valor seja maior que 3000:
1List<Transaction> findByAmountGreaterThan(double amount);
É possível excluir registros da mesma maneira. Dê uma olhada:
1void deleteByTransactionType(String type);
Se tudo estiver funcionando corretamente, seu código deverá ser semelhante a este:
1package com.mongodb;
2import org.springframework.data.mongodb.repository.MongoRepository;
3import org.springframework.stereotype.Repository;
4import java.util.List;
5
6@Repository
7public interface TransactionRepository extends MongoRepository<Transaction, String>{
8 List<Transaction> findByTransactionType(String type);
9 List<Transaction> findByAmountGreaterThan(double amount);
10 void deleteByTransactionType(String type);
11}

@Query

Continuando nosso desenvolvimento, podemos usar a classeQuery que o Spring fornece em vez de trabalhar com consultas derivadas. Esta alternativa tambémé interessante. Abaixo está um exemplo de como buscar transações por status, exibindo ( Projeção) apenas os campos createdAt accountDetail amount, e e classificando por createdAt em ordem decrescente:
1@Query(
2 value= "{ 'status' : ?0 }",
3 fields="{ 'createdAt': 1, 'accountDetails' : 1, 'amount' : 1}",
4 sort = "{ createdAt: -1}"
5)
6List<Transaction> findByStatus(String status);

@Update

Também podemos executar operações de atualização em nosso banco de dados de dados usando esta anotação. Na primeira parte, aplicamos o filtro e, em seguida, atualizamos o status.
1@Query("{ '_id' : ?0 }")
2@Update("{ '$set' : { 'status' : ?1 } }")
3void updateStatus(String id, String status);
Observe que, no código acima, estamos combinando @Query e@Update.

@Aggregation

Outra anotação importante nos dados da Spring é @Aggregation. Como o nome sugere, permite-nos criar estágios de agregação . Vamos imaginar o seguinte cenário:
Queremos retornar o total de amount agrupado por tipo de transação. Para fazer isso, criaremos o seguinte método:
1@Aggregation(pipeline = {
2 "{ '$match': { 'transactionType': ?0 } }",
3 "{ '$group': { '_id': '$transactionType', 'amount': { '$sum': '$amount' } } }",
4 "{ '$project': { 'amount': 1 } }"
5})
6List<Transaction> getTotalAmountByTransactionType(String transactionType);
  • No primeiro estágio, $match, filtramos as transações pelo tipo especificado.
  • No segundo estágio,, $group agrupamos as transações por transactionType e somamos os valores do amount campo.
  • Finalmente, no $project estágio, exibimos o total de amount.
Uma nova solicitação de nossa equipe de produto chegou e eles querem que salvemos todas as transações com um status de erro em uma nova coleção. Podemos definir uma tarefa para ser executada uma vez por dia e acionar trigger um método para lidar com isso para nós. Para isso, podemos usar $out o estágio do MongoDB:
1@Aggregation(pipeline = {
2 "{ '$match': { 'status': 'error' } }",
3 "{ '$project': { '_id': 1, 'amount': 1, 'status': 1, 'description': 1, 'createdAt': 1} }",
4 "{ '$out': 'error_transactions' }"
5})
6void exportErrorTransactions();
Depois que esse método for executado, a agregação será processada e quaisquer documentos com um status de erro serão inseridos em uma nova coleção dentro do mesmo banco de dados de dados chamado error_transactions.
Vamos decompor novamente:
  • $match para filtrar transações com status de erro
  • $project para especificar quais campos queremos incluir na collection de erros
  • $out para definir a coleção para onde exportaremos as transações de erro
Observação: embora Go não entraremos em detalhes sobre as especificidades de $out, é altamente recomendável revisar seu comportamento antes de usá-lo em um ambiente de produção.
Como você pode ver, essa anotação é muito avançada e pode fornecer funcionalidades para queries cada vez mais complexas. Vamos presumir que agora temos uma nova collection chamada customers com alguns campos adicionais:
1{
2 "name": "Ricardo Mello",
3 "email": "ricardo.mello@mongodb.com",
4 "accountNumber": "2234987651",
5 "phone": "5517992384610",
6 "address": {
7 "street": "Street 001",
8 "city": "New York"
9 }
10}
Podemos usar $lookup para realizar uma união entre as transaction customer collections e usando o accountNumber campo e trazer valores extras como phone e address.
1@Aggregation(pipeline = {
2 "{ '$lookup': { " +
3 "'from': 'customer', " +
4 "'localField': 'accountDetails.originator.accountNumber', " +
5 "'foreignField': 'accountNumber', " +
6 "'as': 'originatorCustomerDetails' } }",
7 "{ '$project': { " +
8 "'amount': 1, " +
9 "'status': 1, " +
10 "'accountDetails': 1, " +
11 "'originatorCustomerDetails': 1 } }"
12})
13List<CustomerOriginatorDetail> findTransactionsWithCustomerDetails();
No código acima, estamos usando o $lookup operador para unir a nova customer coleção por meio do accountNumber campo e retornando uma nova lista de CustomerOriginatorDetail.
1public record CustomerOriginatorDetail(
2 double amount,
3 String status,
4 Transaction.AccountDetails accountDetails,
5 List<OriginatorCustomerDetails> originatorCustomerDetails
6) {
7 private record OriginatorCustomerDetails(
8 String name,
9 String accountNumber,
10 String phone,
11 Address address) {
12
13 private record Address(String city, String street) {}
14 }
15}
Observação: lembre-se de que não estamos nos concentrando em modelagem de dados aqui, apenas mostrando que é possível trabalhar com $lookup nesse contexto.

agregação personalizada

Outra abordagem com a @Aggregation anotação é criar uma interface que represente uma agregação, o que simplifica seu uso. Nossa SearchAggregate anotação do permite a criação de funcionalidade de pesquisa reutilizável usando a estrutura de agregação do MongoDB, facilitando a consulta eficiente e estruturada de dados de texto em seu banco de dados de dados.
1import org.springframework.data.mongodb.repository.Aggregation;
2
3import java.lang.annotation.ElementType;
4import java.lang.annotation.Retention;
5import java.lang.annotation.RetentionPolicy;
6import java.lang.annotation.Target;
7
8@Retention(RetentionPolicy.RUNTIME)
9@Target({ ElementType.METHOD })
10@Aggregation(pipeline = {
11 "{ '$search': { 'text': { 'query': ?0, 'path': ?1 } } }"
12})
13@interface SearchAggregate {
14}
Agora, para utilizar nossa anotação em um método, podemos passar os argumentos para a query e o caminho na TransactionRepository classe :
1@SearchAggregate
2List<Transaction> search(String query, String path);

PagingAndSortingRepository

Um aspecto importante a ser observado é que, se procurarmos as interfaces estendidas por MongoRepository, encontraremos PagingAndSortingRepository, que inclui o método:
Page<T> findAll(Pageable pageable);
Este método é crucial para trabalhar com paginação. Vamos Go voltar ao nosso TransactionService e implementar o seguinte código:
1public Page<Transaction> findPageableTransactions(
2 Pageable pageable
3) {
4 return transactionRepository.findAll(pageable);
5}
Agora, no TransactionController, podemos fazer a chamada paginada da seguinte maneira:
1@GetMapping("/pageable")
2public PagedModel<Transaction> findAll(@RequestParam(defaultValue = "0") int page,
3 @RequestParam(defaultValue = "100") int sizePerPage,
4 @RequestParam(defaultValue = "ID") String sortField,
5 @RequestParam(defaultValue = "DESC") Sort.Direction sortDirection) {
6 Pageable pageable = PageRequest.of(page, sizePerPage, Sort.by(sortDirection, sortField));
7 return new PagedModel<>(transactionService.findPageableTransactions(pageable));
8}
E, finalmente, podemos chamar nosso método usando o seguinte curl comando.
1curl --location 'http://localhost:8080/transactions?page=0&sizePerPage=10&sortField=description&sortDirection=ASC'

O MongoTemplate

Uma alternativa muito interessante para obter flexibilidade e controle sobre a operação do MongoDB é o MongoTemplate. Esse modelo oferece suporte a operações como atualização, inserção e seleção, e também fornece uma interação avançada com o MongoDB, permitindo mapear nossos objetos de domínio diretamente para o modelo document model de documento no banco de banco de dados. Vamos começar criando uma nova MongoConfig classe :
1package com.mongodb;
2
3import com.mongodb.client.MongoClient;
4import com.mongodb.client.MongoClients;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.data.mongodb.core.MongoOperations;
8import org.springframework.data.mongodb.core.MongoTemplate;
9
10@Configuration
11public class MongoConfig {
12
13 @Bean
14 public MongoClient mongoClient() {
15 MongoClientSettings settings = MongoClientSettings.builder()
16 .applyConnectionString(new ConnectionString("<your connection string>"))
17 .build();
18 return MongoClients.create(settings);
19 }
20
21 @Bean
22 MongoOperations mongoTemplate(MongoClient mongoClient) {
23 return new MongoTemplate(mongoClient, "springshop");
24 }
25}
Podemos observar a anotação @Bean da Spring, o que nos permite trabalhar com o MongoTemplate injetado em nossos serviços. Continuando com nosso desenvolvimento, trabalharemos com a classe Cliente para manipulá-la. Para fazer isso, crie o Customer registro:
1package com.mongodb;
2
3public record Customer(
4 String name,
5 String email,
6 String accountNumber,
7 String phone,
8 Address address
9) {
10 public record Address(
11 String street,
12 String city
13 ) {}
14}

inserir

Vamos começar com o modelo básico de inserção e depois desenvolver para outras operações. Para fazer isso, criaremos nosso CustomerService com o seguinte método:
1import org.springframework.data.mongodb.core.MongoOperations;
2import org.springframework.stereotype.Service;
3
4@Service
5public class CustomerService {
6
7 private final MongoOperations mongoOperations;
8
9 CustomerService(MongoOperations mongoOperations) {
10 this.mongoOperations = mongoOperations;
11 }
12
13 public Customer newCustomer() {
14 var customer = new Customer(
15 "Ricardo",
16 "ricardohsmello@gmail.com",
17 "123",
18 "1234",
19 new Customer.Address("a", "Sp")
20 );
21
22 return mongoOperations.insert(customer);
23 }
24
25}
Nosso serviço funcionará com o mongoOperations, que lidará com a inserção do nosso documento.
Observe que estou criando (novo cliente) para ilustrar a inserção. Uma boa prática seria receber este cliente como um argumento na nossa função.

Gravação em massa

Uma maneira de lidar com inserções em massa é usando o BulkWrite. O MongoTemplate nos fornece o método bulkOps, onde podemos fornecer uma lista, e ele será executado em lotes:
1public int bulkCustomerSample(List<Customer> customerList) {
2 BulkWriteResult result mongoOperations.bulkOps(BulkOperations.BulkMode.ORDERED, Customer.class)
3 .insert(customerList)
4 .execute();
5
6 return result.getInsertedCount();
7}

Query

Passando para queries, temos a implementação de queries na classe MongoTemplate . No código abaixo, estamos procurando um cliente por e-mail e retornando apenas um:
1public Customer findCustomerByEmail(String email) {
2 return mongoOperations.query(Customer.class)
3 .matching(query(where("email").is(email)))
4 .one()
5 .orElseThrow(() -> new RuntimeException("Customer not found with email: " + email));
6}
O método de query (importado estaticamente) espera um Critério (onde), também importado estaticamente, onde podemos aplicar vários filtros, como gt(), lt() e() e ou(). Para obter referência, consulte a documentação da Spring.

Agregação

Vamos supor que precisamos de uma query que retorne o número total de clientes por cidade. Para isso, precisamos agrupar por cidade e contar o número de clientes. Para atingir esse objetivo, primeiro, vamos criar um novo registro para lidar com isso:
1public record CustomersByCity(
2 String id,
3 int total
4){}
Em seguida, criaremos um método totalCustomerByCity:
1public List<CustomersByCity> totalCustomerByCity() {
2
3 TypedAggregation<Customer> aggregation = newAggregation(Customer.class,
4 group("address.city")
5 .count().as("total"),
6 Aggregation.sort(Sort.Direction.ASC, "_id"),
7 project(Fields.fields("total", "_id")));
8
9 AggregationResults<CustomersByCity> result = mongoOperations.aggregate(aggregation, CustomersByCity.class);
10 return result.getMappedResults();
11}
O método estático newAggregation nos oferece uma boa abordagem para manipular nossos estágios de agregação . Estamos agrupando por cidade e usando a função count(), que realiza internamente uma soma sobre o número total de clientes, classificando por _id _id em ordem crescente e exibindo os ID campos total e ID.

Conclusão

Nesta segunda parte de nossa série, Spring Data Unlocked, exploramos como criar queries complexas com MongoRepository e MongoTemplate. Este tutorial abordou conceitos como paginação, anotações personalizadas, inserções em massa e consultas de agregação avançadas.
O código completo está disponível em mongo-developer.
Se você tiver alguma dúvida, fique à vontade para deixá-la nos comentários.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Artigo

Capture dados de IoT com o MongoDB em 5 minutos


Sep 09, 2024 | 2 min read
Tutorial

Otimize e ajuste o desempenho do MongoDB com índices ocultos


Oct 01, 2024 | 5 min read
Início rápido

Tutorial do MongoDB e Node.js - Operações CRUD


Aug 22, 2023 | 17 min read
Início rápido

Introdução a pipelines de agregação em Rust


Oct 01, 2024 | 15 min read
Sumário
  • Pré-requisitos