Reactive Java Spring Boot with MongoDB
Teo Wen Jie5 min read • Published Apr 02, 2024 • Updated Apr 02, 2024
FULL APPLICATION
Spring Boot +
Reactive +
Spring Data +
MongoDB. Putting these four technologies together can be a challenge, especially if you are just starting out.
Without getting into details of each of these technologies, this tutorial aims to help you get a jump start on a working code base based on this technology stack.
This tutorial features:
- Interacting with MongoDB using ReactiveMongoRepositories.
- Interacting with MongoDB using ReactiveMongoTemplate.
This simplified cash balance application allows you to make REST API calls to:
- Create or fetch an account.
- Perform transactions on one account or between two accounts.
Access the repository README for more details on the functional specifications.
The README also contains setup, API usage, and testing instructions. To clone the repository:
Let's do a logical walkthrough of how the code works.
I would include code snippets, but to reduce verbosity, I will exclude lines of code that are not key to our understanding of how the code works.
This section showcases how you can perform Create and Read operations with
ReactiveMongoRepository
.This snippet shows two endpoints:
- A POST method endpoint that creates an account
- A GET method endpoint that retrieves an account but throws an exception if it cannot be found
They both simply return a
Mono<Account>
from AccountRepository.java,
a ReactiveMongoRespository
interface which acts as an abstraction from the underlying
Reactive Streams Driver..save(...)
method creates a new document in the accounts collection in our MongoDB database..findByAccountNum()
method fetches a document that matches theaccountNum
.
The @Query annotation
allows you to specify a MongoDB query with placeholders so that it can be dynamically substituted with values from method arguments.
?0
would be substituted by the value of the first method argument and ?1
would be substituted by the second, and so on and so forth.The built-in query builder mechanism
can actually determine the intended query based on the method's name.
In this case, we could actually exclude the @Query annotation
but I left it there for better clarity and to illustrate the previous point.
Notice that there is no need to declare a
save(...)
method even though we are actually using accountRepository.save()
in AccountController.java.
The save(...)
method, and many other base methods, are already declared by interfaces up in the inheritance chain of ReactiveMongoRepository
.This section showcases:
- Update operations with
ReactiveMongoRepository
. - Create, Read, and Update operations with
ReactiveMongoTemplate
.
Back to
AccountController.java
:This snippet shows three endpoints:
- A
.../debit
endpoint that adds to an account balance - A
.../credit
endpoint that subtracts from an account balance - A
.../transfer
endpoint that performs a transfer from one account to another
Notice that all three methods look really similar. The main idea is:
- A
Txn
can consist of one to manyTxnEntry
. - A
TxnEntry
is a reflection of a change we are about to make to a single account. - A debit or credit
Txn
will only have oneTxnEntry
. - A transfer
Txn
will have twoTxnEntry
. - In all three operations, we first save one record of the
Txn
we are about to perform, and then make the intended changes to the target accounts using the TxnService.java.
The
updateBalances(...)
method is responsible for iterating through each TxnEntry
and making the corresponding updates to each account.
This is done by calling the findAndIncrementBalanceByAccountNum(...)
method
in AccountRespository.java.Similar to declaring
find
methods, you can also declare Data Manipulation Methods
in the ReactiveMongoRepository
, such as update
methods.
Once again, the query builder mechanism
is able to determine that we are interested in querying by accountNum
based on the naming of the method, and we define the action of an update using the @Update
annotation.
In this case, the action is an $inc
and notice that we used ?1
as a placeholder because we want to substitute it with the value of the second argument of the method.Moving on, in
TxnService
we also have:- A
saveTransaction
method that saves aTxn
document intotransactions
collection. - A
executeTxn
method that callsupdateBalances(...)
and then updates the transaction status in theTxn
document created.
Both utilize the
TxnTemplate
that contains a ReactiveMongoTemplate
.The
ReactiveMongoTemplate
provides us with more customizable ways to interact with MongoDB and is a thinner layer of abstraction compared to ReactiveMongoRepository
.In the
findAndUpdateStatusById(...)
method, we are pretty much defining the query logic by code, but we are also able to specify that the update should return the newly updated document.The transfer feature in this application is a perfect use case for multi-document transactions because the updates across two accounts need to be atomic.
In order for the application to gain access to Spring's transaction support, we first need to add a
ReactiveMongoTransactionManager
bean to our configuration as such:With this, we can proceed to define the scope of our transactions. We will showcase two methods:
1. Using TransactionalOperator
The
ReactiveMongoTransactionManager
provides us with a TransactionOperator
.We can then define the scope of a transaction by appending
.as(transactionalOperator::transactional)
to the method call.2. Using @Transactional annotation
We can also simply define the scope of our transaction by annotating the method with the
@Transactional
annotation.We are done! I hope this post was helpful for you in one way or another. If you have any questions, visit the MongoDB Community, where MongoDB engineers and the community can help you with your next big idea!
Once again, you may access the code from the GitHub repository,
and if you are just getting started, it may be worth bookmarking Spring Data MongoDB.
Top Comments in Forums
There are no comments on this article yet.