A Voyage AI se une ao MongoDB para impulsionar aplicativos de AI mais precisos e confiáveis no Atlas.

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 .

Inicialização reativa do Java Spring com MongoDB

Teo Wen Jie5 min read • Published Apr 02, 2024 • Updated Apr 02, 2024
APLICATIVO COMPLETO
Facebook Icontwitter iconlinkedin icon
Classificar este exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty

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 repositório README para mais detalhes sobre as especificações funcionais. O README também contém instruções de configuração, uso da API e testes. 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 as 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 realizar operações de criação e leitura com ReactiveMongoRepository.
Os pontos de conexão 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 ContaRepository.java, uma interface doReactiveMongoRespository 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() método obtém 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 pelas interfaces na cadeia de herança do .ReactiveMongoRepository

Debit, crédito e transferência

Esta seção mostra:
  • Operações de atualização com ReactiveMongoRepository.
  • Criar, ler e atualizar operações 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 de um a muitos .TxnEntry
  • Um TxnEntry é o reflexo de uma alteração que estamos prestes a fazer em uma única conta.
  • Um Txncrédito ou crédito só terá 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 os métodosupdate. Mais uma vez, o mecanismo construtor de query é capaz de determinar que estamos interessados na query por accountNum com base no nome do método, e definimos a ação de uma atualização usando a anotação @Update . Nesse caso, a ação é$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 o.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. Utilizando 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
Forum Commenter Avatar
Sai_Kumar_TataSai Kumar Tataúltimo trimestre

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.


Facebook Icontwitter iconlinkedin icon
Classificar este exemplo de código
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Início rápido

Pipeline de agregação Java


Oct 01, 2024 | 8 min read
Tutorial

Como construir um modelo de detecção de fraudes em Java usando o aprendizado profundo4J


Jan 27, 2025 | 15 min read
Podcast

Expansão do setor de jogos com Gaspard Petit, da Square Enix


Mar 22, 2023 | 29 min
Artigo

Dados da primavera desbloqueados: técnicas de otimização de desempenho com o MongoDB


Dec 04, 2024 | 5 min read