Skip to content

Instantly share code, notes, and snippets.

@normoes
Last active June 19, 2024 04:01
Show Gist options
  • Save normoes/53c46a3ef2bbe3a1bff817573362f6ee to your computer and use it in GitHub Desktop.
Save normoes/53c46a3ef2bbe3a1bff817573362f6ee to your computer and use it in GitHub Desktop.
mongodb aggregates using MongoRepository in Java spring boot

mongodb aggregates using MongoRepository in Java spring boot


Some prerequisites and assumptions

Use the following dependencies within your pom.xml:

  • spring-boot-starter-parent 1.5.7.RELEASE
  • spring-boot-starter-data-mongodb

The main class Application.java in com.example is annotated with @SpringBootApplication, so it is recognized by spring boot to run as a spring boot application.

A mongo server should be running at localhost:27017. Because docker is such a great tool, I use it whenever I can. Running a mongodb server is just the right thing for docker.

So, this is the command to run mongodb version 3.2.7 within a docker container (very simple):

docker run --rm -p 27017:27017 mongo:3.2.7

As you certainly know, the application's configuration (ports, hosts, ...) can also be modified in the application.properties file.

It is at least necessary - in this very basic setup - to configure a mongodb database name. Let's just call this database appdb:

spring.data.mongodb.database=appdb # Database name.
spring.data.mongodb.repositories.enabled=true # Enable Mongo repositories.

If you should experience any problems, please refer to the the srping boot mongodb guide. This project gives a very short introduction and also provides an example projects.


The use case

Let's just assume, you want to store Todos.

And let's further assume, you might want to implement a kind of reminder functionality. In fact, as programmers, we actually would like to end up with a list of unfinished Todos.

So, to keep it breif and easy, let's just focus on getting the list of unfinished Todos - The actual reminding will be part of another gist.

Assume an unfinished Todo to be defined in the following way:

  • No one is assigned to this Todo by now.
  • Since the creation of the Todo more time has passed than has been defined.

To achieve this, a Todo needs a creation time as well as a reminder time. Those two values plus the current time are necessary to calculate, if the Todo should be added to the list - thus triggering a reminder.

The Data model

This could be the Todo class.

Todo.java:

package com.example.models;

@Document(collection = "todos")
public class Todo {
	@Id
	private String _id;  // set by mongodb
	private String name;  // name of the Todo
	private String assignedTo;  // the one the Todo is for
	private long created;  // unix time epoch [seconds]
	private int reminder=120;  // if not yet finished, remind [seconds]
	
	// For abbreviation's sake, I left out the getters and setters.
	// ...
}

The whole class is annotated with @Document, so spring-boot-mongodb knows, it represents a mongodb document, which is to be stored within the todos collection of the database appdb.

Besides it is important to tell spring-boot-mongodb about an @id annotated attribute - in our case _id. This annotated field will be the ObjectId attribute in the mongodb document and is set automatically by mongodb itself whenever data is stored into the database or updated.

The data type of _id is a String, but internally (mongodb) _id is cast to ObjectId.

It is quite easy to connect to a mongo database in spring boot.

The connection is handled completely by spring-boot-mongodb. It's almost like magic ;)

All what needs to be done is to create a repository interface.

The repository interface looks like this:

TodoRepo.java:

package com.example.repos;
import com.example.models.Todo;

public interface TodoRepo extends MongoRepository<Todo, String> {

}

By default (due to spring-boot's magic), this setup allows database transcations like the following ones:

@Autowired TodoRepo todoRepo;

todoRepo.findAll();
todoRepo.findOne("5a8165b5fae0f6040b888929");  // String representation of an ObjectId

Spring-boot-mongodb's query functionalities already are very sophisticated since class attrbiutes can be queried and combined in elaborate ways.

The spring boot docs happen to be very resourceful in that matter.

So far, it is possible to store data into the database and then run queries on the data.

At this point, we also could get our list of unfinished Todos by just querying for all the Todos and looping over them. Adding some conditions and comparisons would eventually result in a list of unfinihsed Todos.

But!

It would be way better, if mongodb itself would do all the work. Tn the end, this means less looping and comparing and cleaner code.

Problem

It is not possbile to aggregate.

How to combine mongodb aggregates with Java and spring boot?

Now, if you desired to get all the unfinihsed Todos, you would have to get them all from mongodb. Then the task of comparing the Todo's attributes created and remindAfter needs to be done within the backend (some loops and comparisons), when mongo could have done most of the work.

This is how I would like to obtain the unfinished Todos:

@Autowired TodoRepo todoRepo;

long now = TimeFunctions.getCurrentTimestampInSecs();  // unix time epoch
List<ToDo> todos = todoRepo.findAllUnfinishedTodos(now);

Extend the exisiting MongoRepository.

Create an interface TodoRepoCustom.java, that defines the extension to the existing repository TodoRepo.java.

ATTENTION Due to how spring boot is working internally it is very important to call this interface the same as the original one (TodoRepo in our case) including a trailing Custom.

TodoRepoCustom.java:

package com.example.repos;
import com.example.models.Todo;

public interface TodoRepoCustom {
	List<ToDo> findAllUnfinishedTodos(long now);
}

and its implementation TodoRepoImpl.java containing the actual aggregation using an instance of MongoTemplate:

TodoRepoImpl.java:

public class TodoRepoImpl implements TodoRepoCustom  {
	    
    private final MongoTemplate mongoTemplate;
    
    @Autowired
    public TodoRepoImpl(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }
    
    @Override    
    public List<Todo> findAllUnfinishedTodos(long now) {
    	List<AggregationOperation> list = new ArrayList<AggregationOperation>();
    	list.add(Aggregation.match(Criteria.where("created").gt(0)));
    	list.add(Aggregation.sort(Sort.Direction.ASC, "created"));
    	list.add(Aggregation.project("_id").andExpression(String.valueOf(now) + " - created >= remindAfer"));
    	
    	TypedAggregation<Todo> agg = Aggregation.newAggregation(Todo.class, list);
        return mongoTemplate.aggregate(agg, Todo.class, Todo.class).getMappedResults();
    }
)

The actual repository (TodoRepo.java) now also needs to extend our new interface TodoRepoCustom.java. So let's modify it:

TodoRepo.java:

package com.example.repos;
import com.example.models.Todo;

public interface TodoRepo extends MongoRepository<Todo, String>, TodoRepoCustom {

}

So, what happened?

The repository TodoRepo additionally extends the interface TodoRepoCustom and therefore provides List<ToDo> findAllUnfinishedTodos(long now);.

ATTENTION Due to how spring boot is working internally it is very important to call this interface the same as the original one (TodoRepo in our case) including a trailing Custom.

Of course the implementation TodoRepoImpl is the most important part, since it defines the actual functionality - according to the defintion above.


The previous steps made it possible to use mongodb aggregates in the MongoRepository and optimizing the backend by reducing it by some unnecessary loops and comparisons.

The list returned only contains the appropriate Todos - In our case the unfinished Todos.

@mrityush
Copy link

Very helpful. Thanks!

@jimcoun
Copy link

jimcoun commented Oct 21, 2020

Thank you for this guide!

@dtrunk90
Copy link

You can simply use the @Aggregation annotation.

@nareshkm
Copy link

nareshkm commented Oct 6, 2022

Wow, perfect !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment