We had at work a piece of code that was using mongorepository and updating the arrays of the object on a collection instead of using mongodb method directly.
The way it was coded had a big issues with concurrency and we were losing update because of it.
The problem we had with the way it was updating the array and saving using mongorepository and we are going to fix it using directly mongo queries with MongoTemplate.
Example of code with mongorepository
@Autowired
private MongoContentRepository mongoContentRepository;
private void addStudentToSchool(final Student student) {
Optional<School> optionalSchool = mongoContentRepository.findById(student.getSchoolId());
if (optionalSchool.isPresent()) {
School school = optionalSchool.get();
List<Student> students = school.getStudents();
if (students != null && !students.isEmpty()) {
Optional<Student> optionalStudent = getStudentInSchool(students, student);
optionalStudent.ifPresentOrElse(studentInDb -> {
if (hasStudentChanged(studentInDb, student)) {
students.remove(studentInDb);
students.add(student);
}
}, () -> students.add(student));
school.setStudents(students);
} else {
school.setStudents(List.of(students));
}
mongoContentRepository.save(school);
}
}
There are a lot of things wrong here.
It saving the whole object each time, which means if there a student is save in mongodb while another one is being save in a longer transaction, it will override the first update.
Using MongoTemplate
You can prevent that problem, by using directly mongo queries with MongoTemplate
Reference: https://docs.mongodb.com/manual/reference/operator/update/positional/
db.getCollection('content').update(
{_id : "1000102088"},
{$set : {"student.0.name" : "Joe Smith"}}
)
Or even better (in case the operation might change the order of the element in the array):
db.getCollection('content').update(
{_id : "1000102088", student: {$elemMatch: {_id : "1000102177"}}},
{$set : {"student.$.name" : "Joe Smith"}}
)
What does that look like in Java :
If we take the first query and convert to the equivalent code with MongoTemplate:
public void addStudentInSchool(String objectId, Student student, int index) {
mongoTemplate.updateFirst(
Query.query(Criteria.where("_id").is(objectId)),
new Update().set("student.$[" + index + "]", student), COLLECTION_NAME);
}
For the second query we get:
public void updateStudentInSchool(String schoolId, Student student) {
mongoTemplate.updateFirst(
Query.query(Criteria.where("_id")
.is(objectId)
.andOperator(Criteria.where("student")
.elemMatch(Criteria.where("_id").is(student.getId())))),
new Update().set("student.$", student), COLLECTION_NAME);
}
Note: WP code block seems to mess up the indentation.
End result
If we take our initial code and use our mongo template code we get the following end result.
Let’s update addStudentToSchool with MongoTemplate instead of mongoContentRepository:
private static final String COLLECTION_NAME = "schoolCollection";
private final MongoTemplate mongoTemplate;
public void addStudentInSchool(String schoolId, Student student) {
mongoTemplate.updateFirst(
Query.query(Criteria.where("_id").is(schoolId)),
new Update().addToSet("student", student), COLLECTION_NAME);
}
public void updateStudentInSchool(String schoolId, Student student) {
mongoTemplate.updateFirst(
Query.query(Criteria.where("_id").is(schoolId).andOperator(Criteria.where("student").elemMatch(Criteria.where("_id").is(student.getId())))),
new Update().set("student.$", student), COLLECTION_NAME);
}
private void addStudentToSchool(final Student student) {
Optional<School> optionalSchool = mongoContentRepository.findById(student.getSchoolId());
if (optionalSchool.isPresent()) {
School school = optionalSchool.get();
List<Student> students = school.getStudents();
if (students != null && !students.isEmpty()) {
Optional<Student> optionalStudent = getStudentInSchool(students, student);
optionalStudent.ifPresentOrElse(
studentInDb -> contentCollection.updateStudentInSchool(school.getId(), student),
() -> contentCollection.addStudentInSchool(school.getId(), student));
} else {
contentCollection.addStudentInSchool(school.getId(), student);
}
}
This worked fine for us and we stop having our mongdb update disappearing due to concurrency.
Why did it solve the concurrency issue ?
All $inc $set $unset $push $pushAll $addToSet $pop $pull $pullAll $rename $bit operations are atomic.
This mean if multiple $set or $addToSet are done in concurrency all the operations and lock that entry.
For more information on mongodb concurrency:
https://stackoverflow.com/questions/6997835/how-does-mongodb-deal-with-concurrent-updates
https://docs.mongodb.com/manual/faq/concurrency/
Hope It helped you and have a nice day.
how to update the value in existing list suppose i have list of string [“abc”] and then i want to update this list for example [“abc”] to [“abc”,”xyz”]. how to do this?