Reputation: 379
I am trying to update a array field in a embedded document in mongodb using Spring Data MongoDB.
The structure of document which I want to be updated/inserted in mongodb collection is as below.
There can be more documents like this based on each type of the department , say "Sales","Marketing" and so on.
{
"timestamp": "2014-09-26T04:00:00.000Z",
"department": "accounts",
"employee": [
{
"type": "regular",
"names": [
"Raj",
"Kumar",
"Shankar"
]
},
{
"type": "contract",
"names": [
"Penny",
"Sheldon",
"bob"
]
},
{
"type": "temp",
"names": [
"jerry",
"kramer",
"bubbleboy"
]
}
]
}
Basically, I have my update query as below,
db.getCollection('mytest').update(
{
"timestamp" : "2014-09-26T04:00:00.000Z",
"department" : "accounts",
"employee.type" : "regular"
},
{ $addToSet: { "employee.$.names" : "Jo" } },
{
upsert: true
}
)
I have added upsert: true because if there is no document matching the query I want to insert the document into the mytest collection.
When I execute the same from mongo shell , I am getting the below error.
The positional operator did not find the match needed from the query. Unexpanded update: employee.$.names
Even If this works, I am not sure if we have similar support for implementing the same in Spring Data mongodb.
Also, my other query is, if I want to add/update employees for multiple departments, say "accounts" as well as "sales", it appears that I have to execute the same query with different values as the number of department I want to update ( if not present insert) accordingly.
Is there a better and efficient option like bulk/batch where-in I can update/insert employees for multiple departments at the same time in a single mongo update query. Also, is there support in spring data mongodb/mongotemplate for the same.
Upvotes: 1
Views: 4447
Reputation: 39226
Firstly, I should say that there are multiple scenarios that need to be handled as the document structure has embedded arrays. If possible, please try to redesign the document structure.
Short answer to error:- You get the position operator error if the query doesn't find the matching document.
That's why in my below solution, I have handled the scenario in catch block. If you can go through the unit test, you should be able to understand.
Solution:-
public Boolean updateDepartmentCollectionWithoutFind(String name, String timeStamp)
throws JsonParseException, JsonMappingException, IOException {
MongoOperations mongoOperations = getMongoConnection();
Query query = new Query();
query.addCriteria(Criteria.where("timestamp").is(timeStamp).and("department").is("accounts")
.and("employee.type").is("regular"));
Update update = new Update();
update.addToSet("employee.$.names", name);
try {
System.out.println(query.toString());
System.out.println(update.toString());
WriteResult writeResult = mongoOperations.upsert(query, update, DepartmentCollection.class);
if (writeResult != null) {
System.out.println("111111111111111111 Update with position parameter has been successful :"
+ writeResult.toString());
}
} catch (DataIntegrityViolationException die) {
System.out.println("Update failed ====>" + die.getMessage());
System.out.println("Trying to update without position parameters ...");
Update updateWithoutPositionOperator = new Update();
updateWithoutPositionOperator.addToSet("employee.names", name);
WriteResult writeResultUpsert = mongoOperations.upsert(query, updateWithoutPositionOperator,
DepartmentCollection.class);
if (writeResultUpsert != null) {
System.out.println("2222222222222222222 Update without position parameter has been successful :"
+ writeResultUpsert.toString());
}
}
return true;
}
My get connection method for reference:- You don't need this if you have the spring context and can change the above code based on your context.
@SuppressWarnings("resource")
public MongoOperations getMongoConnection() {
return (MongoOperations) new AnnotationConfigApplicationContext(SpringMongoConfig.class)
.getBean("mongoTemplate");
}
Unit Tests:- I am posting the unit test because you need to understand the scenario and solution. Please read the comment in each test and method name to understand which upsert gets executed on the each scenarios.
Test 3 is a special behavior. Please closely look at it.
@Test
public void updateDepartmentCollectionWhenNoDocumentsPresent() throws JsonParseException, JsonMappingException, IOException {
//Second upsert executed (i.e. without positional parameter)
Assert.isTrue(queryOperations.updateDepartmentCollectionWithoutFind("Raj", "2014-09-26T04:00:00.000Z"));
}
@Test
public void updateDCWhenMatchingDocumentsPresentButDocumentHasOnlyRegularEmployeeType() throws JsonParseException, JsonMappingException, IOException {
//Second upsert executed (i.e. without positional parameter)
Assert.isTrue(queryOperations.updateDepartmentCollectionWithoutFind("Jo", "2014-09-26T04:00:00.000Z"));
}
@Test
public void updateDCWhenMultipleTypesPresentButNoRegular() throws JsonParseException, JsonMappingException, IOException {
//First upsert executed (i.e. with positional parameter). However, it creates a new document.
Assert.isTrue(queryOperations.updateDepartmentCollectionWithoutFind("Jo", "2014-09-27T04:00:00.000Z"));
}
@Test
public void updateDCWhenMultipleTypesPresentIncludingRegular() throws JsonParseException, JsonMappingException, IOException {
//First upsert executed (i.e. with positional parameter) and existing document has been updated.
Assert.isTrue(queryOperations.updateDepartmentCollectionWithoutFind("Jo", "2014-09-29T04:00:00.000Z"));
}
Upvotes: 1