This article assumes that you have worked through the steps in MongoDB: New Database to set up a MongoDB deployment on the site https://cloud.mongodb.com, with:
- a cluster than can accessed from
0.0.0.0/0
, i.e. from any IP address - a username and password with read/write access
- a database called
database
and a collection calledposts
From that starting point, we’ll explain:
- How to set up a Java class that represents a single
Document
in the collection - How to set up a Java class that represents the entire collection
- How to use those classes in backend controller methods to do CRUD operations on the collection.
Setting up the pom.xml
To work with MongoDB, we should add the following dependency to our pom.xml
. Check the URL in the comment for the latest version; at this writing that is 2.6.3
but it may be later by the time you are reading this.
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-mongodb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
If using the the spring-boot-starter-parent
as many of our projects are, i.e. the code below, then it is not necessary to specify a version; that will be determined by the version in the <parent>
element:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
Otherwise consult the URL in the comment to determine the latest version to use.
Adjusting our collections
If you followed the MongoDB: New Database to set up your database, you have:
- a database named
database
- in that database, a collection named
posts
To see that, here are the navigation steps you need:
-
Select your organization:
)
-
Select your project:
)
-
When you see the cluster where your project is deployed, you can either click the
Browse Collections
button:)
Or you can click into the cluster, and then select the Collections tab:
)
When you get there, you see this:
)
Renaming a collection
The first thing we will try to do is rename posts
to reddit_posts
.
Note that as far as I can tell, at the current time, there is no way to rename a collection in the web user interface.
Fortunately, since our collection is empty, we can just drop it and recreate it.
To drop the collection posts, hover over posts
and you’ll see a trash can icon pop up:
)
Then, click the trash can and a confirmation modal appears. Type in posts
and click Drop
)
That returns us to a state of having no collections, which looks like this:
)
From here, we’ll click Add your own data
and create two collections: reddit_posts
and students
. The user interface here is a bit tricky, so I’ll walk you through it. First click Add my own data
which gets us here, where you can fill in database
and reddit_posts
)
That results in this:
)
Then, to create the second collection (or additional ones after that), hover over the name database
, and you’ll see a plus sign pop up. Click that to add the second collection.
)
Enter students
as the name of the second collection:
)
Now we have a database named database
and two collections: reddit_posts
and students
. We are ready to start doing Java coding.
Configuration for a connection to MongoDB
The place that you are supposed to set up Mongo DB Connection URI is in .env
file (copied from .env.SAMPLE
) as the value of the variable MONGODB_URI
.
Here’s where to get the value of MONGODB_URI
:
In the left hand navigation on the https://cloud.mongodb.com site, find Database
in the left nav, and get to this page:
)
Then, click where it says Connect
. That brings up this Modal. Click the “Connect Your Application” link:
)
That brings up this. Do not worry about setting the programming language and version.
- That matters only if you are looking for example code, and it turns out the code the MongoDB site offers for Java is low level code that is not appropriate for Spring Boot anyway.
- The part that does matter, the connection string, is the same across all programming languages.
)
Copy the connection string, and paste it in as the value for MONGODB_URI
. But you are not finished. There is still the step of filling in the password, and replacing the myFirstDatabase
with the correct name of the database.
MONGODB_URI=mongodb+srv://readWrite01:<password>@cluster0.v6z4u.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
First, let’s deal with the database, since that’s easy. If you called your database database
, then just change myFirstDatabase
to database
, like this:
MONGODB_URI=mongodb+srv://readWrite01:<password>@cluster0.v6z4u.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
Now, that places where it says <password>
needs to be replaced with the correct password. To do this, navigate back to Database Access
in the left nav:
)
Click Edit
beside the user you are going to use.
As an aside: note that you can have multiple users with different privilege levels, so if your application only needs read access to the data, it may be better to configure a read only user for your web app, and the upload data using a different user with a script. (We’ll provide an example of an uploader script in a different article.) For now, though, we’ll use a user with both read and write privileges.
Clicking Edit
next to a user brings up this:
)
Click where it says “Edit password”. That opens up this:
)
Note that it is not possible to look up the password, only to change it. So, we are going to autogenerate a new-password, and then paste it into our .env
file in place of password
. (Note that example I’m using here is a fake one; so don’t bother trying to hack with it.)
Click Autogenerate, then Show, so you see something like this:
)
Now copy the value BkZGP3TZZJG0dAfE
and paste it into your .env
file in place of the password. Do not include the <>
around <password>
- Correct:
MONGODB_URI=mongodb+srv://readWrite01:BkZGP3TZZJG0dAfE@cluster0.v6z4u.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
- Incorrect:
MONGODB_URI=mongodb+srv://readWrite01:<BkZGP3TZZJG0dAfE>@cluster0.v6z4u.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
Now, the part that everyone always forgets: Do not just click the X or navigate away from the window! The password is not saved unless you scroll down to the bottom of the modal and click Update User
!
)
But the documentation says spring.data.mongodb.uri
?
You may wonder: why MONGODB_URI
when the Spring Boot documentation (and/or various tutorials) say that it should be set via spring.data.mongodb.uri
?
The place to set up the connection string is indeed the application property spring.data.mongodb.uri
; however, it is a bad idea to hard code usernames and passwords in the application.property file:
spring.data.mongodb.uri=DO-NOT-HARD-CODE-THE-URI-HERE
Instead, we set it up to pick up the value from an environment variable MONGODB_URI
and put in a fake default value that can be used as a fallback so that the code will at least run to some extent if/when we fail to set a value:
spring.data.mongodb.uri=${MONGODB_URI:${env.MONGODB_URI:mongodb+srv://fakeUsername:fakePassword@cluster0.ulqcw.mongodb.net/fakeDatabase?retryWrites=true&w=majority}}
This says: try to get a property MONGODB_URI
; failing that, try to get MONGODB_URI
from the environment, failing that, use this hard coded fake default.
Note that the application will at least start up with fake values for the username, password, and database, but if the host in the URI does not exist, you may get a fatal error on startup (i.e. at the mvn spring-boot:run
stage.)
Setting up a Document class
A MongoDB document is a representation of a JSON object. As such, it can be nested arbitrarily deep.
The same is true when setting up a Java object to represent a MongoDB document.
Suppose we have a simple JSON object, such as the following JSON object representing a student with a name and a perm:
{
"first_name" : "Chris",
"last_name" : "Gaucho",
"perm" : 1234567
}
In this case, it is straightforward to make a class Student.java
that represents objects of this type. But our first step will be to create a directory under src/main/java/
that is a sibling of our controllers
, services
, entities
, etc. called documents
, like this:
Before | After |
---|---|
) | ) |
Under that folder, create Student.java
like this:
package edu.ucsb.cs156.example.documents;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Document(collection = "students")
public class Student {
@Id
private String id;
private String firstName;
private String lastName;
private int perm;
}
Then, create a folder called collections
:
)
In that folder, create a file StudentCollection.java
like this:
package edu.ucsb.cs156.example.collections;
import java.util.Optional;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import edu.ucsb.cs156.example.documents.Student;
@Repository
public interface StudentCollection extends MongoRepository<Student, ObjectId> {
Optional<Student> findOneByPerm(int perm);
Optional<Student> findOneByFirstNameAndLastName(String firstName, String lastName);
}
Finally, we can create a controller, StudentsController.java
package edu.ucsb.cs156.example.controllers;
import edu.ucsb.cs156.example.collections.StudentCollection;
import edu.ucsb.cs156.example.documents.Student;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Api(description = "Students")
@RequestMapping("/api/students")
@RestController
@Slf4j
public class StudentsController extends ApiController {
@Autowired
StudentCollection studentCollection;
@Autowired
ObjectMapper mapper;
@ApiOperation(value = "List all students")
@PreAuthorize("hasRole('ROLE_USER')")
@GetMapping("/all")
public Iterable<Student> allStudents() {
loggingService.logMethod();
Iterable<Student> students = studentCollection.findAll();
return students;
}
@ApiOperation(value = "Add a Student to the collection")
@PreAuthorize("hasRole('ROLE_USER')")
@PostMapping("/post")
public Student postStudent(
@ApiParam("firstName") @RequestParam String firstName,
@ApiParam("lastName") @RequestParam String lastName,
@ApiParam("perm") @RequestParam int perm) {
loggingService.logMethod();
Student student = new Student();
student.setFirstName(firstName);
student.setLastName(lastName);
student.setPerm(perm);
// OR
// Student student = Student.builder()
// .firstName(firstName)
// .lastName(lastName)
// .perm(perm)
// .build();
Student savedStudent = studentCollection.save(student);
return savedStudent;
}
}
This will give us endpoints that we can test with Swagger:
)
A POST gives us a new Student:
)
)
And a GET shows that the student was added to the Collection:
)
)
Finally, we can navigate to our Collection on https://cloud.mongodb.com and see that the data is in the Collection:
)
Next steps: A more complex collection
As you can see by the code above, it is relatively straightforward to set up a Document and a Collection for a simple flat JSON object.
However, for simple flat object with no nesting, an SQL database may be a better choice anyway. The power of MongoDB is really for representing hierarchical nested JSON data.
So in the next article, we’ll look at RedditPosts, which have several layers of complexity, and describe how to set up a Java class hierarchy to represent these objects.