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.
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 Todo
s.
So, to keep it breif and easy, let's just focus on getting the list of unfinished Todo
s - 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.
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
.
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 Todo
s by just querying for all the Todo
s and looping over them. Adding some conditions and comparisons would eventually result in a list of unfinihsed Todo
s.
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.
Now, if you desired to get all the unfinihsed Todo
s, 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 Todo
s:
@Autowired TodoRepo todoRepo;
long now = TimeFunctions.getCurrentTimestampInSecs(); // unix time epoch
List<ToDo> todos = todoRepo.findAllUnfinishedTodos(now);
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 {
}
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 Todo
s - In our case the unfinished Todo
s.
You can simply use the @Aggregation annotation.