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 .

Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
Javachevron-right

Inicialização reativa do Java Spring com MongoDB

Teo Wen Jie5 min read • Published Apr 02, 2024 • Updated Apr 02, 2024
SpringMongoDBJava
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
social-githubVer código

Introdução

Spring Boot + Reactive + Spring Data + MongoDB. Unir essas quatro tecnologias pode ser um desafio, especialmente se você está apenas começando. Sem entrar em detalhes de cada uma dessas tecnologias, este tutorial tem como objetivo ajudá-lo a dar um salto inicial em uma base de código funcional com base nessa pilha de tecnologias. Este tutorial apresenta:
  • Interagindo com o MongoDB usando ReactiveMongoRepositores.
  • Interagindo com o MongoDB usando o ReactiveMongoTemplate.
  • Agrupando consultas em uma transação ACID multidocumento.
Esse aplicativo simplificado de saldo de caixa permite fazer chamadas da REST API para:
  • Criar ou buscar uma conta.
  • Realizar transações em uma conta ou entre duas contas.

Repositório do GitHub

Acesse o README do repositório para obter mais detalhes sobre as especificações funcionais. O README também contém instruções de configuração, uso da API e teste. Para clonar o repositório:
1git clone git@github.com:mongodb-developer/mdb-spring-boot-reactive.git

Passo a passo do código

Vamos fazer um passo a passo lógico de como o código funciona. Eu incluiria trechos de código, mas para reduzir a verbosidade, excluirei linhas de código que não são essenciais para nossa compreensão de como o código funciona.

Criando ou buscando uma conta

Esta seção mostra como você pode executar operações de criação e leitura com ReactiveMongoRepository.
Os endpoints da API para criar ou buscar uma conta podem ser encontrados em accountController.java:
1@RestController
2public class AccountController {
3 //...
4 @PostMapping("/account")
5 public Mono<Account> createAccount(@RequestBody Account account) {
6 return accountRepository.save(account);
7 }
8
9 @GetMapping("/account/{accountNum}")
10 public Mono<Account> getAccount(@PathVariable String accountNum) {
11 return accountRepository.findByAccountNum(accountNum).switchIfEmpty(Mono.error(new AccountNotFoundException()));
12 }
13 //...
14}
Este trecho mostra dois endpoints:
  • Um endpoint do método POST que cria uma conta
  • Um endpoint do método GET que recupera uma conta, mas lança uma exceção se ela não puder ser encontrada
Ambos simplesmente retornam um Mono<Account> de AccountRepository.java, uma interfaceReactiveMongoRespository que atua como uma abstração do Reactive Streams Driversubjacente.
  • .save(...) O método cria um novo documento na coleção de contas em nosso MongoDB database.
  • .findByAccountNum() O método busca um documento que corresponde ao accountNum.
1public interface AccountRepository extends ReactiveMongoRepository<Account, String> {
2
3 @Query("{accountNum:'?0'}")
4 Mono<Account> findByAccountNum(String accountNum);
5 //...
6}
A anotação @Query permite especificar uma query do MongoDB com espaços reservados para que ela possa ser substituída dinamicamente por valores de argumentos de método. ?0 seria substituído pelo valor do primeiro argumento de método e ?1 seria substituído pelo segundo, e assim por diante.
O mecanismo de construtor de queryembutido pode realmente determinar a query destinada com base no nome do método. Nesse caso, poderíamos excluir a anotação @Query, mas deixei-a lá para maior clareza e para ilustrar o ponto anterior.
Observe que não há necessidade de declarar um métodosave(...), embora estejamos usando accountRepository.save() no accountcontroller.java. O métodosave(...) e muitos outros métodos básicos já estão declarados por interfaces na cadeia de herança de ReactiveMongoRepository.

Debit, crédito e transferência

Esta seção mostra:
  • Atualizar operações com ReactiveMongoRepository.
  • Operações de criação, leitura e atualização com ReactiveMongoTemplate.
Voltar para AccountController.java:
1@RestController
2public class AccountController {
3 //...
4 @PostMapping("/account/{accountNum}/debit")
5 public Mono<Txn> debitAccount(@PathVariable String accountNum, @RequestBody Map<String, Object> requestBody) {
6 //...
7 txn.addEntry(new TxnEntry(accountNum, amount));
8 return txnService.saveTransaction(txn).flatMap(txnService::executeTxn);
9 }
10
11 @PostMapping("/account/{accountNum}/credit")
12 public Mono<Txn> creditAccount(@PathVariable String accountNum, @RequestBody Map<String, Object> requestBody) {
13 //...
14 txn.addEntry(new TxnEntry(accountNum, -amount));
15 return txnService.saveTransaction(txn).flatMap(txnService::executeTxn);
16 }
17
18 @PostMapping("/account/{from}/transfer")
19 public Mono<Txn> transfer(@PathVariable String from, @RequestBody TransferRequest transferRequest) {
20 //...
21 txn.addEntry(new TxnEntry(from, -amount));
22 txn.addEntry(new TxnEntry(to, amount));
23 //save pending transaction then execute
24 return txnService.saveTransaction(txn).flatMap(txnService::executeTxn);
25 }
26 //...
27}
Este snippet mostra três endpoints:
  • Um endpoint.../debit que adiciona ao saldo de uma conta
  • Um endpoint.../credit que subtrai do saldo de uma conta
  • Um endpoint.../transfer que executa uma transferência de uma conta para outra
Observe que todos os três métodos são muito semelhantes. A ideia principal é:
  • Um Txn pode consistir em um para muitos TxnEntry.
  • Um TxnEntry é o reflexo de uma alteração que estamos prestes a fazer em uma única conta.
  • Um débito ou crédito Txn terá apenas um TxnEntry.
  • Uma transferência Txn terá dois TxnEntry.
  • Em todas as três operações, primeiro salvamos um registro do Txn que estamos prestes a executar e, em seguida, usamos o TxnService.javapara fazer as alterações desejadas nas contas de destino.
1@Service
2public class TxnService {
3 //...
4 public Mono<Txn> saveTransaction(Txn txn) {
5 return txnTemplate.save(txn);
6 }
7
8 public Mono<Txn> executeTxn(Txn txn) {
9 return updateBalances(txn)
10 .onErrorResume(DataIntegrityViolationException.class
11 /*lambda expression to handle error*/)
12 .onErrorResume(AccountNotFoundException.class
13 /*lambda expression to handle error*/)
14 .then(txnTemplate.findAndUpdateStatusById(txn.getId(), TxnStatus.SUCCESS));
15 }
16
17 public Flux<Long> updateBalances(Txn txn) {
18 //read entries to update balances, concatMap maintains the sequence
19 Flux<Long> updatedCounts = Flux.fromIterable(txn.getEntries()).concatMap(
20 entry -> accountRepository.findAndIncrementBalanceByAccountNum(entry.getAccountNum(), entry.getAmount())
21 );
22 return updatedCounts.handle(/*...*/);
23 }
24}
O métodoupdateBalances(...) é responsável por iterar cada TxnEntry e fazer as atualizações correspondentes a cada conta. Isso é feito chamando o métodofindAndIncrementBalanceByAccountNum(...) em accountRespository.java.
1public interface AccountRepository extends ReactiveMongoRepository<Account, String> {
2 //...
3 @Update("{'$inc':{'balance': ?1}}")
4 Mono<Long> findAndIncrementBalanceByAccountNum(String accountNum, double increment);
5}
Semelhante à declaração de métodosfind, você também pode declarar métodos de manipulação de dados no ReactiveMongoRepository, como métodosupdate . Mais uma vez, o mecanismo do construtor de consultas é capaz de determinar que estamos interessados em consultar por accountNum com base na nomenclatura do método e definimos a ação de uma atualização usando a anotação@Update. Nesse caso, a ação é um $inc e observe que usamos ?1 como espaço reservado porque queremos substituí-lo pelo valor do segundo argumento do método.
Seguindo em frente, em TxnService também temos:
  • Um métodosaveTransaction que salva um documento Txnna collectiontransactions.
  • Um métodoexecuteTxn que chama updateBalances(...) e atualiza o status da transação no documentoTxn criado.
Ambos utilizam o TxnTemplate que contém um ReactiveMongoTemplate.
1@Service
2public class TxnTemplate {
3 //...
4 public Mono<Txn> save(Txn txn) {
5 return template.save(txn);
6 }
7
8 public Mono<Txn> findAndUpdateStatusById(String id, TxnStatus status) {
9 Query query = query(where("_id").is(id));
10 Update update = update("status", status);
11 FindAndModifyOptions options = FindAndModifyOptions.options().returnNew(true);
12 return template.findAndModify(query, update, options, Txn.class);
13 }
14 //...
15}
O ReactiveMongoTemplate nos fornece maneiras mais personalizáveis de interagir com MongoDB e é uma camada mais fina de abstração em comparação com ReactiveMongoRepository.
No métodofindAndUpdateStatusById(...), estamos definindo praticamente a lógica da query por código, mas também podemos especificar que a atualização deve retornar o documento recém-atualizado.

Transações ACID multidocumento

O recurso de transferência neste aplicativo é um caso de uso perfeito para transações com vários documentos, pois as atualizações em duas contas precisam ser atômicas.
Para que o aplicativo obtenha acesso ao suporte de transações do Spring, primeiro precisamos adicionar um ReactiveMongoTransactionManager bean à nossa configuração da seguinte forma:
1@Configuration
2public class ReactiveMongoConfig extends AbstractReactiveMongoConfiguration {
3 //...
4 @Bean
5 ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory dbFactory) {
6 return new ReactiveMongoTransactionManager(dbFactory);
7 }
8}
Com isso, podemos definir o escopo de nossas transações. Mostraremos dois métodos:
1. Como usar o TransactionalOperator
O ReactiveMongoTransactionManager nos fornece um TransactionOperator.
Podemos então definir o escopo de uma transação anexando .as(transactionalOperator::transactional) à chamada do método.
1@Service
2public class TxnService {
3 //In the actual code we are using constructor injection instead of @Autowired
4 //Using @Autowired here to keep code snippet concise
5 @Autowired
6 private TransactionalOperator transactionalOperator;
7 //...
8 public Mono<Txn> executeTxn(Txn txn) {
9 return updateBalances(txn)
10 .onErrorResume(DataIntegrityViolationException.class
11 /*lambda expression to handle error*/)
12 .onErrorResume(AccountNotFoundException.class
13 /*lambda expression to handle error*/)
14 .then(txnTemplate.findAndUpdateStatusById(txn.getId(), TxnStatus.SUCCESS))
15 .as(transactionalOperator::transactional);
16 }
17 //...
18}
2. Usando a anotação @Transactional
Também podemos simplesmente definir o escopo de nossa transação anotando o método com a anotação@Transactional.
1public class TxnService {
2 //...
3 @Transactional
4 public Mono<Txn> executeTxn(Txn txn) {
5 return updateBalances(txn)
6 .onErrorResume(DataIntegrityViolationException.class
7 /*lambda expression to handle error*/)
8 .onErrorResume(AccountNotFoundException.class
9 /*lambda expression to handle error*/)
10 .then(txnTemplate.findAndUpdateStatusById(txn.getId(), TxnStatus.SUCCESS));
11 }
12 //...
13}
Leia mais sobre transações e sessões no Spring Data MongoDB para obter mais informações.

Conclusão

Terminamos! Esperemos que esta publicação tenha sido útil para você de uma forma ou de outra. Se você tiver alguma dúvida, visite a MongoDB Community, onde os engenheiros do MongoDB e a comunidade podem ajudá-lo com sua próxima grande ideia!
Mais uma vez, você pode acessar o código a partir do repositório GitHube, se estiver apenas começando, pode valer a pena marcar o Spring Data MongoDBcomo favorito.
Principais comentários nos fóruns
Avatar do Comentarista do Fórum
Sai_Kumar_TataSai Kumar Tata2 meses atrás

Conforme documento oficial, o MongoDB tem certas limitações em transações reativas.
Sessões e transações :: Spring Data MongoDB

Como podemos superar isso.

Veja mais nos fóruns

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Introdução ao Azure Spring Apps e ao MongoDB Atlas: um guia passo a passo


Jan 27, 2024 | 5 min read
Início rápido

Como criar um aplicativo CRUD com MongoDB, Quarkus e GraalVM


Aug 29, 2024 | 7 min read
Tutorial

Como se conectar ao MongoDB com um proxy SOCKS5 com Java


Aug 29, 2024 | 2 min read
Tutorial

Geração aumentada de recuperação com MongoDB e Spring AI: Trazendo AI para seus aplicativos Java


Sep 23, 2024 | 6 min read
Tecnologias Utilizadas
Linguagens
Tecnologias
Produtos
Sumário
  • Introdução