Outline:
In this tutorial, we are going to learn how to implement a sequential and auto-generated field for MongoDB in Spring Boot. If you are new to MongoDB and Spring then visit Spring Data MongoDB Tutorial.Understanding step by step today about "Auto-Generated Field for MongoDB using Spring Boot"
Spring Boot Tutorials
[update] Updated new errors in this article.
This is a very common scenario that arises when using MongoDB and Spring framework integration work.
When we are using MongoDB databases as part of the Spring Boot application directly we can not use @GeneratedValue annotation in our database model classes.
@GeneratedValue: Marking a field with the @GeneratedValue annotation specifies that value will be automatically generated for that field and mainly this is used on primary key fields. This annotation is available only for JPA API.
The easy solution to this problem is very simple. We will create a collection or table that will store the generated sequence for other collections. During the creation of a new record, we’ll use it to fetch the next value.
Maven Dependencies for spring boot and mongodb:
We should add the required dependencies in the pom.xml file as below. Get the latest version from here.<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <versionId>2.1.0.RELEASE</versionId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> <versionId>2.1.0.RELEASE</versionId> </dependency> </dependencies>
Using Collections:
1) First, create a collection that will store the auto-incremented value in it. This can be created using either the mongo shell or MongoDB Compassimport org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "database_sequences") public class DatabaseSequence { @Id private String id; private long seq; public DatabaseSequence() {} public String getId() { return id; } public void setId(String id) { this.id = id; } public long getSeq() { return seq; } public void setSeq(long seq) { this.seq = seq; } }
2. Let’s then create a users_db collection. This collection stores the users that are being used.
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.mongodb.core.mapping.Document; @Document(collection = "users_db") public class User { @Transient public static final String SEQUENCE_NAME = "users_sequence"; @Id private long id; private String firstName; private String lastName; private String email; public User() { } public User(String firstName, String lastName, String email) { this.firstName = firstName; this.lastName = lastName; this.email = email; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + '}'; } }
Here SEQUENCE_NAME is declared as constant and annotated with @Transient to prevent it from serializing to persist in the table.
Creating a New Record - Auto-Generated for MongoDB:
1) Creating a UserRepository.
import com.java-w3schools.mongodb.models.User; import org.springframework.data.mongodb.repository.MongoRepository; public interface UserRepository extends MongoRepository{ }
2) Creating Service
@Service public class SequenceGeneratorService { private MongoOperations mongoOperations; @Autowired public SequenceGeneratorService(MongoOperations mongoOperations) { this.mongoOperations = mongoOperations; } 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; } }
3) Creating Listener
@Component public class UserModelListener extends AbstractMongoEventListener{ private SequenceGeneratorService sequenceGeneratorService; @Autowired public UserModelListener(SequenceGeneratorService sequenceGeneratorService) { this.sequenceGeneratorService = sequenceGeneratorService; } @Override public void onBeforeConvert(BeforeConvertEvent event) { if (event.getSource().getId() < 1) { event.getSource().setId(sequenceGeneratorService.generateSequence(User.SEQUENCE_NAME)); } } }
4) Calling generateSequence
User user = new User(); user.setId(sequenceGeneratorService.generateSequence(User.SEQUENCE_NAME)); user.setEmail("java-w3schools@example.com"); userRepository.save(user);
Here a unique id will be generated in sequence when we invoke generateSequence() method each time.
TroubleShooting
Problem:
org.springframework.dao.InvalidDataAccessApiUsageException: Cannot autogenerate id of type java.lang.Long for entity of type ua.home.springdata.investigation.entity.Account! at org.springframework.data.mongodb.core.MongoTemplate.assertUpdateableIdIfNotSet(MongoTemplate.java:1149) at org.springframework.data.mongodb.core.MongoTemplate.doSave(MongoTemplate.java:878) at org.springframework.data.mongodb.core.MongoTemplate.save(MongoTemplate.java:833) at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:73) at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:88) at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:442) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:427) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) at com.sun.proxy.$Proxy26.save(Unknown Source)
Suggestion 1:
Mongo ObjectIds don't map to a java Long type.
I see this in the documentation, under 7.6.1:
An id property or field declared as a String in the Java class will be converted to and stored as an ObjectId if possible using a Spring Converter. Valid conversion rules are delegated to the MongoDB Java driver. If it cannot be converted to an ObjectId, then the value will be stored as a string in the database.
An id property or field declared as BigInteger in the Java class will be converted to and stored as an ObjectId using a Spring Converter.
So change id to a String or a BigInteger and remove the strategy argument.
Suggestion 2:
Using @Id as a String works fine.
Make sure that your Repository extends with a String (same type as the @Id):
extends MongoRepository<MyEntity, String>
Suggestion 3:
Add maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
Source:
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "ACCOUNTS")
public class Account {
@Id
private String id;
....rest of properties
}
import org.springframework.data.mongodb.repository.MongoRepository;
public interface AccountRepository extends MongoRepository<Account, String> {
//any extra queries needed
}
No comments:
Post a Comment
Please do not add any spam links in the comments section.