1. Overview
In this tutorial, weβre going to learn how to implement a sequential, auto-generated field for MongoDB in Spring Boot.
When weβre using MongoDB as the database for a Spring Boot application, we canβt use @GeneratedValue annotation in our models as itβs not available. Hence we need a method to produce the same effect as weβll have if weβre using JPA and an SQL database.
The general solution to this problem is simple. Weβll create a collection (table) thatβll store the generated sequence for other collections. During the creation of a new record, weβll use it to fetch the next value.
2. Dependencies
Letβs add the following spring-boot starters to our pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
The latest version for the dependencies is managed by spring-boot-starter-parent.
3. Collections
As discussed in the overview, weβll create a collection thatβll store the auto-incremented sequence for other collections. Weβll call this collection database_sequences. It can be created using either the mongo shell or MongoDB Compass. Letβs create a corresponding model class:
@Document(collection = "database_sequences")
public class DatabaseSequence {
@Id
private String id;
private long seq;
//getters and setters omitted
}
Letβs then create a users collection, and a corresponding model object, thatβll store the details of people that are using our system:
@Document(collection = "users")
public class User {
@Transient
public static final String SEQUENCE_NAME = "users_sequence";
@Id
private BigInteger id;
private String email;
//getters and setters omitted
}
In the User model created above, we added a static field SEQUENCE_NAME, which is a unique reference to the auto-incremented sequence for the users collection. Also for auto-generation of an ObjectId to succeed, the type of the Id property or field in your class must be a String, an ObjectId, or a BigInteger.
We also annotate it with the @Transient to prevent it from being persisted alongside other properties of the model.
4. Creating a New Record
So far, weβve created the required collections and models. Now, weβll create a service thatβll generate the auto-incremented value that can be used as id for our entities.
Letβs create a SequenceGeneratorService that has generateSequence():
public long generateSequence(String seqName) {
DatabaseSequence counter = mongoOperations.findAndModify(query(where("_id").is(seqName)),
new Update().inc("seq",1), options().returnNew(true).upsert(true),
DatabaseSequence.class);
return !Objects.isNull(counter) ? counter.getSeq() : 1;
}
Now, we can use the generateSequence() while creating a new record:
User user = new User();
user.setId(sequenceGenerator.generateSequence(User.SEQUENCE_NAME));
user.setEmail("[email protected]");
userRepository.save(user);
To list all the users, weβll use the UserRepository:
List<User> storedUsers = userRepository.findAll();
storedUsers.forEach(System.out::println);
As it is now, we have to set the id field every time we create a new instance of our model. We can circumvent this process by creating a listener for Spring Data MongoDB lifecycle events.
To do that, weβll create a UserModelListener that extends AbstractMongoEventListener<User> and then weβll override the onBeforeConvert():
@Override
public void onBeforeConvert(BeforeConvertEvent<User> event) {
if (event.getSource().getId().intValue() < 1) {
event.getSource().setId(BigInteger.valueOf(
sequenceGenerator.generateSequence(User.SEQUENCE_NAME)));
}
}
Now, every time we save a new User, the id will be set automatically.
5. Conclusion
In conclusion, weβve seen how to generate sequential, auto-incremented values for the id field and simulate the same behavior as seen in SQL databases.
Hibernate uses a similar method for generating auto-incremented values by default.
