Overview
Spring Data is an umbrella project which contains many submodules, each specific to a particular database. In this article, we'll be covering Spring Data MongoDB by building an application that stores and retrieves data from MongoDB, a document based NO-SQL database.
If you'd like to read more about Spring Data, we've covered it in detail in - Guide to Spring Data JPA.
MongoDB
MongoDB is a document-oriented NoSQL database that stores JSON-like documents with dynamic schemas. It is commonly used for high volume data storage.
Before moving forward, it would be good to know some of the NoSQL database terms. Please note that these terms are not exactly a one-to-one in comparison with relational SQL databases:
- Database: It is a container for collections and can be thought of as similar to an RDBMS database, which is a container for Tables.
- Collection: It is equivalent to Tables in RDBMS, but unlike a collection it has a dynamic schema. A collection exists within a database.
- Document: It is a single record in a MongoDB collection. It can be thought of as a row in RDBMS.
- Field: A document has zero or more fields. It's like an RDBMS column having a key-value pair.
To setup MongoDB server on your local machine you can download the installable here according to your operating system. You can then also download a tool like Compass for a nice GUI to interact with your server.
Another option and the one which we will be using is MongoDB Atlas, which is a cloud database as a service. After you sign up, login and build a cluster using the free tier:
To connect to our cluster, we have to create a user:
Let's now create our database and collection:
We are now set to connect to our collection using our Spring application.
Spring Data MongoDB Project
Setup
The best way to start with a skeleton project is to visit Spring Initializr. Select your preferred version of Spring Boot and add the Web
and MongoDB
dependencies:
After this, generate it as a Maven project and you're all set!
Defining a Collection
First, let's define our collection Candidate
model class:
@Document(collection = "candidate")
public class Candidate {
@Id
private String id;
private String name;
private double exp;
@Indexed(unique = true)
private String email;
// getters and setters
}
Now let's take a look at these annotations:
- @Document: This marks the class as a domain object that will be persisted into the database. The default collection name that is used is the class name (first character lowercased). We can map to a different collection in the database by using the
collection
attribute of the annotation. - @Id: This marks the field used for identity purposes.
- @Indexed(unique = true): This is applied to the field that will be indexed with a constraint of unique.
Defining Repository
We create a repository by making an interface:
public interface CandidateRepository extends MongoRepository<Candidate, String> {}
CandidateRepository
extends the MongoRepository
interface and plugs in the datatype of the document that we are working with, i.e Candidate
and String
respectively.
This will give us access to all the CRUD operations around the MongoDB collection.
Connection Setup
To set up a proper connection, we need to define the connection properties in application.properties
:
spring.data.mongodb.uri=mongodb+srv://<USERNAME>:<PASSWORD>@<ClUSTER-NAME>-<INSTANCE-ID>/<DATABASE-NAME>?retryWrites=true
You can get these values directly from the MongoDB Atlas UI:
Note: If your password contains special characters then it must be URL encoded.
By default, your cluster is secured not to take requests from any client IP. We need to allow our IP to be able to connect to this cluster via an IP Whitelist:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Defining the Controller
Now, let's use our repository in our CandidateController
via the @Autowired
annotation:
@RestController
@RequestMapping("/candidate")
public class CandidateController {
@Autowired
private CandidateRepository candidateRepository;
Simple CRUD Operations
Insert
Let's create a POST
mapping that will insert data to our MongoDB:
@PostMapping
@ResponseStatus(code = HttpStatus.CREATED)
public Candidate add(@RequestBody Candidate candidate) {
return candidateRepository.save(candidate);
}
We used the save()
method on the candidateRepository
object. The Candidate
object is captured by @RequestBody
and is used directly in the save()
method.
If we try to use the same email ID again, we will get a duplicate key error:
We can check our collection status in Atlas too:
Read
Let's create a couple of GET
mappings to fetch our records.
@GetMapping
public List<Candidate> getAll() {
return candidateRepository.findAll();
}
@GetMapping(value = "/{id}")
public Candidate getOne(@PathVariable String id) {
return candidateRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException());
}
findAll()
will return all the records in our database, while the findById()
method will return a single record based on the ID passed.
If the record is not present it throws
a custom runtime exception. ResourceNotFoundException
is a custom class that returns 404
status if it's thrown:
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException() {
}
}
If you'd like to read more about this, we've covered it in detail in - Exception Handling in Spring.
Update
Now to update a particular record, we'll use a PUT
mapping:
@PutMapping(value = "/{id}")
public Candidate update(@PathVariable String id, @RequestBody Candidate updatedCandidate) {
Candidate candidate = candidateRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException());
candidate.setName(updatedCandidate.getName());
candidate.setExp(updatedCandidate.getExp());
candidate.setEmail(updatedCandidate.getEmail());
return candidateRepository.save(candidate);
}
We first check whether the Candidate
with the given id
is present or not. If not, we return a 404
status, otherwise we update the whole object and save it using the save()
method:
Delete
Now, let's delete a particular record by using the DELETE
mapping:
@DeleteMapping(value = "/{id}")
@ResponseStatus(code = HttpStatus.ACCEPTED)
public void delete(@PathVariable String id) {
Candidate candidate = candidateRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException());
candidateRepository.delete(candidate);
}
We used the delete()
method on the candidateRepository
to delete the entry:
Custom Query Methods
We can add some methods to our CandidateRepository
to have some additional functionality based on our business requirements:
public interface CandidateRepository extends MongoRepository<Candidate, String> {
Optional<Candidate> findByEmail(String email);
List<Candidate> findByExpGreaterThanEqual(double exp);
List<Candidate> findByExpBetween(double from, double to);
}
Above, we added search functionality based on email and experience. All we need to do is follow a naming convention laid down by Spring Data.
After the findBy()
method we write the name of the attribute in camel case, followed by any other restriction that we may want to enforce. The arguments to the method should match the where clause expectation. Spring Data will create actual queries for you during the startup of the application by using this interface.
Let's use this in our controller:
@GetMapping("/searchByEmail")
public Candidate searchByEmail(@RequestParam(name = "email") String email) {
return candidateRepository.findByEmail(email)
.orElseThrow(() -> new ResourceNotFoundException());
}
@GetMapping("/searchByExp")
public List<Candidate> searchByExp(@RequestParam(name = "expFrom") Double expFrom, @RequestParam(name = "expTo", required = false) Double expTo) {
List<Candidate> result = new ArrayList<>();
if (expTo != null) {
result = candidateRepository.findByExpBetween(expFrom, expTo);
} else {
result = candidateRepository.findByExpGreaterThanEqual(expFrom);
}
return result;
}
Conclusion
In this article, we've covered how to use Spring Data MongoDB to connect to a MongoDB server. We first created a MongoDB server in the cloud using MongoDB Atlas and then used Spring Data to connect to it. After that, we performed some simple CRUD operations as well as wrote some custom queries.
As always, the code for the examples used in this article can be found on GitHub.