Implement Custom Mongo Client Integration in Spring Boot

Salitha Chathuranga
5 min readJul 1, 2023

--

Let’s create our own Mongo Client

Repository layer is the place where we connect to database, usually in Spring Boot. Simply the DAO layer. In this case, DB would a Mongo DB. So, we need a configuration setup to connect to Mongo DB.

Do you know how we configure Mongo DB straightly using env configs in Spring Boot? I hope so… Simple 2️⃣ steps were there…

  1. Place spring.data.mongodb.uri value in the application.properties file, which is your local or cloud DB cluster URL.
  2. Create Repository interface with your DAO object.

That’s all! Am I right? This works perfectly fine. But it is not the only way. We can go for a code level implementation also with custom env configs.

Here comes the new part in this article! 😎

I will follow a approach which is bit different than you have seen so far. It’s not the traditional way you connect a Mongo database to a Spring Boot app. Simply, I’m going to override the Repository implementation. Let’s see…

Imagine we have a REST API to manipulate Student records. I will brief details here.

Setup Environment

So, I will go with a code level implementation which takes DB related env values runtime from the .env file and application.properties. I’m going to use 4️⃣ main configs and 3️⃣ test configs to setup the DB for application and tests both.

Basic Configurations

These are the values used to authenticate and create Mongo connection. The values will be injected to the application runtime.

database.cluster=${DB_CLUSTER}
database.username=${DB_USERNAME}
database.dbname=${DB_NAME}
database.password=${DB_PASSWORD}

Test Configurations

These configs are only needed for tests. But to keep env consistency, we have to add it into main resources also.

mongo.db.test.enabled=true
mongo.db.test.url=mongodb://localhost:27080
mongo.db.docker.image=mongo:5.0.14

#1 — Config to detect whether we are on test running mode or not. It will be switched on only while tests are running.

mongo.db.test.enabled => true in test/resources
mongo.db.test.enabled => false in main/resources

#2 — Config to provide Mongo DB test URL dynamically to application context. Value can be any hard coded URL. However it will be dynamically overridden within the code while tests are running, with the URI we get from runtime docker container initialized after integrating test containers. It’s an additional step taken for integration/functional tests. I will come up with a article later on how we use these values.

mongo.db.test.url = mongodb://localhost:27070 => main and test resources

#3 — Config to provide mongo DB image name to initiate docker container. We can provide any image name with tag. It will be used while running integration/functional tests with test containers.

mongo.db.docker.image => mongo:5.0.14 in test/resources

Following the provided information above, find the completed env setup below.

.env File

DB_NAME=student-system
DB_CLUSTER=cluster0.xxx.mongodb.net
DB_USERNAME=admin
DB_PASSWORD=xxxxxxxx

main/resources/application.properties File

server.port=8080
spring.config.import=optional:file:.env[.properties]
database.cluster=${DB_CLUSTER}
database.username=${DB_USERNAME}
database.dbname=${DB_NAME}
database.password=${DB_PASSWORD}
mongo.db.test.url=mongodb://localhost:27080
mongo.db.test.enabled=false

test/resources/application.properties File

server.port=8080
spring.config.import=optional:file:.env[.properties]
database.cluster=${DB_CLUSTER}
database.username=${DB_USERNAME}
database.dbname=${DB_NAME}
database.password=${DB_PASSWORD}
mongo.db.docker.image=mongo:5.0.14
mongo.db.test.url=mongodb://localhost:27080
mongo.db.test.enabled=true

So these are the env values. Let’s use them in the code level Mongo Client configuration.

Integrate Mongo Client Config

As I told before, this is a custom implementation. So, we have to create a configuration class and extend it from AbstractMongoClientConfiguration to get the inbuilt config capabilities. Let’s do it!

Now, I will be using the above env values heavily. 😎

Steps:

  1. Inject env values such as database name, cluster, username, password from env using “@Value” annotation.
  2. Override mongoClient method to implement connection. If test mode is enabled (mongo.db.test.enabled => true) we create the test DB client. This will be our docker container initialize using test containers. If test mode is disabled, we connect to real database which is a cloud/local one.
package com.rest.api.configuration;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;

@Configuration
public class MongoConfiguration extends AbstractMongoClientConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoConfiguration.class);

@Value("${database.cluster}")
private String cluster;

@Value("${database.dbname}")
private String database;

@Value("${database.username}")
private String username;

@Value("${database.password}")
private String password;

@Value("${mongo.db.test.url}")
private String testDbConnectionString;

@Value("${mongo.db.test.enabled}")
private boolean isTestEnabled;

@Override
public @NonNull MongoClient mongoClient() {
if (isTestEnabled) {
LOGGER.info("Connecting to Mongo DB: {}", testDbConnectionString);
return MongoClients.create(testDbConnectionString);
} else {
LOGGER.info("Connecting to Mongo DB: {}", getConnectionString(true));
final ConnectionString connectionString = new ConnectionString(getConnectionString(false));
final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
}

protected String getConnectionString(boolean isLogsEnabled) {
StringBuilder mongoConnectionString = new StringBuilder();
mongoConnectionString.append("mongodb+srv://").append(username).append(":");
if (isLogsEnabled) {
mongoConnectionString.append("[*******]");
} else {
mongoConnectionString.append(password);
}
mongoConnectionString.append("@").append(cluster).append("/");
mongoConnectionString.append(database).append("?retryWrites=true&w=majority");
return mongoConnectionString.toString();
}

@Override
protected @NonNull String getDatabaseName() {
return database;
}
}

If you don’t want to switch the code for tests, you can modify the code by removing the if conditions and other unwanted env injections.

Since this is a “@Configuration”, Spring Boot will take care of initializing the required components automatically. It will be creating the Mongo DB connection while starting up the application. You can try it now!!! Check the logs while starting up…

Logs from application startup

Integrate Repository Layer

So, now we have connection. Only remaining part is to connect to the Repository layer and then inject it into the service layer.

Let’s create the interface as we usually do. Here, StudentDAO is my DAO object.

public interface StudentRepository extends MongoRepository<StudentDAO, String> {
}

We don’t need to add “@Repository” annotation here. Why? So, this is not the actual component we want. We are going to create custom one. So, we must add this annotation in the custom class!!!

Let’s create the actual implemented class…

@Repository
public class StudentRepositoryImpl extends SimpleMongoRepository<StudentDAO, String> implements StudentRepository {

public StudentRepositoryImpl(MongoOperations mongoOperations) {
super(new MongoRepositoryFactory(mongoOperations).getEntityInformation(StudentDAO.class), mongoOperations);
}

}

You can see I have used multiple inheritance with interfaces here. I implemented the class with previous StudentRepository interface and SimpleMongoRepository interface.

SimpleMongoRepository => This is the base implementation for Mongo which can be used for custom client integration. It has all the capabilities that MongoRepository has. If you open this class, you can see it has been implemented by MongoRepository.

🔴 Note: Now you can only autowire StudentRepositoryImpl class. You cannot autowire StudentRepository. Keep that on your mind!

Service Layer:

@Service
public class StudentService {

@Autowired
private StudentRepositoryImpl studentRepository;

public List<StudentDAO> getAllStudents() {
return studentRepository.findAll();
}
}

All done! ❤️

So, this is the most simplest way to create a custom mongo configuration setup as I feel. I have used this way and tested already in a deployment environment. So, try this and let me know if you have any concern!

I will bring you another article on using test containers with this Mongo DB setup. 😎

Bye guys!

--

--

Salitha Chathuranga

Associate Technical Lead at Sysco LABS | Senior Java Developer | Blogger