I recently spent some time trying to use the $pull operator through Spring's Data MongoOperations interface, so I thought it would be nice to share my findings in case anyone bumps into a similar problem.
So here it goes...
I have 2 java POJOs like so :
#Document
public class OutterObject{
private String id;
private String name;
private List<InnerDocument> innerDocs;
//SETTERS - GETTERS Ommited
public class InnerDocument{
private String id;
private String name;
//SETTERS - GETTERS Ommited
This is stored in a Mongo collection like so :
"_id" : "doc2",
"_class" : "OutterObject",
"name" : "doc2",
"innerDocs" : [{
"_id" : "innerDoc21",
"name" : "innerDoc21"
}, {
"_id" : "innerDoc22",
"name" : "innerDoc22"
}]
I'm trying to use the $pull operator in order to remove all objects inside the innerDoc collection having a name value = "innerDoc22".
I know how to accomplish this using the mongo driver like so :
List<String> ids =
Arrays.asList("innerDoc22");
BasicDBObject find = new BasicDBObject();
match.put("innerDocs.name",
BasicDBObjectBuilder.start("$in", ids).get());
BasicDBObject update = new BasicDBObject();
update.put(
"$pull",
BasicDBObjectBuilder.start("innerDocs",
BasicDBObjectBuilder.start("name", "innerDoc22").get()).get());
DBCollection col= mongoOperations.getDb().getCollection("outterObject");
col.update(find , update);
I'm trying to accomplish the same thing using Spring's MongoOperations Interface.
Here is my code using the MongoOperations interface :
List<String> ids = Arrays.asList("innerDoc22");
Query removeQuery = Query.query(Criteria.where("innerDocs.name").in(ids));
WriteResult wc = mongoOperations.upsert(
removeQuery,
new Update().pull("innerDocs.name", "innerDoc22"),
OutterObject.class);
System.out.println(wc.getLastError());
I'm not getting any errors when calling getLastError() the update is simply not done in the database.
I know a similar question has already been asked here but the answer that was given does not use the MongoOperations interface.
After searching a bit and looking at the source code I realized that I needed to pass an InnerDocument object as a second parameter to the pull method so that the spring classes would be able to do the mapping correctly.
As it turns out I can navigate objects while selecting objects (I'm using "innerDocs.name" in the removeQuery) but I cannot (or havent found a way) do the same when updating a document.
Below is how I implemented the query using MongoOperations :
List<String> ids = Arrays.asList("innerDoc22", "innerDoc21");
Query removeQuery = Query.query(Criteria.where("innerDocs.name").in(ids));
WriteResult wc =
mongoOperations.upsert(removeQuery,
new Update().pull("innerDocs",
new InnerDocument("innerDoc22", null)),
OutterObject.class);
System.out.println(wc.getLastError());
You can also use the BasicDBObject instead of the InnerDocument I found this out by accident. By printing out the update object, you can see the actual mongo shell query json, which is super helpful for debugging.
:
Update updateObj = new Update()
.pull("innerDocs", new BasicDBObject("innerDocs.name","innerDoc22"));
System.out.println("UPDATE OBJ: " + updateObj.toString());
results in:
UPDATE OBJ: { "$pull" : { "innerDocs" : { "innerDocs.name" : "innerDoc22"}}}
I tried the solution given by med116 and I had to modify it to work :
Update updateObj = new Update().pull("innerDocs", new BasicDBObject("name","innerDoc22"));
Because otherwise there was no matching object in my array.
in spring data mongo,just like this:
//remove array's one elem
UpdateResult wc = mongoTemplate.upsert(removeQuery,new Update().pull("tags",Query.query(Criteria.where("tag").is("java"))),TestPull.class);
//remove array's multi-elem
UpdateResult wc1 = mongoTemplate.upsert(removeQuery,new Update().pull("tags",Query.query(Criteria.where("tag").in(Arrays.asList("mongo", "netty")))),TestPull.class);
If you simply want to remove an element from array which does not have any other property like name then write the query you wish and
Update update = new Update();
update.pull("Yourarrayname","valueyouwishtodelete");
mongoTemplate.upsert(query,update,"collectionname");
That's my solution - thanks to #ufasoli:
public Mono<ProjectChild> DeleteCritTemplChild(String id, String idch) {
Query query = new Query();
query.addCriteria(Criteria
.where("_id")
.is(id)
.and("tasks._id")
.is(idch)
);
Update update = new Update();
update.set("tasks.$", null);
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
);
}
This works for me!
UpdateResult updateResult = mongoTemplate.updateMulti(new Query(where("id").is(activity.getId())), new Update().pull("comments", new Query(where("id").is(commentId))), Activity.class);
It will generate:
Calling update using query: { "_id" : { "$oid" : "61f900e7c7b79442eb3855ea"}} and update: { "$pull" : { "comments" : { "_id" : "61fac32e3f9ab5646e016bc8"}}} in collection: activity
Related
I am getting a String with the structure of JSONArray [ { "abc" : "123" }, { "def" : "456" } ]which I need to use to call mongoCollection.aggregate(theString);
The aggregate function takes List<? extends Bson> and I am not sure what is the best way to convert the String to List<? extends Bson>.
For find() method which takes Bson var1 I am just converting the String to Document using Document.parse(theString);
I am using mongodb 3.4.
I was able to come up with this but it looks a little ugly.
JSONArray array = new JSONArray(theString);
List<Document> list = new ArrayList<>();
for(Object jsonObject : jsonArray){
Document document = Document.parse(jsonObject.toString());
list.add(document);
}
collection.aggregate(list);
I am new to Spring Data Mongo. I've a scenario where I want to create a Study if already not present in mongo db. If its already present, then I've to update it with the new values.
I tried in the following way, which works fine in my case, but I'm not sure this is the correct/Best/Advisable way to update etc as far as performance is concerned.
Could anyone please guide on this?
public void saveStudy(List<Study> studies) {
for (Study study : studies) {
String id = study.getId();
Study presentInDBStudy = studyRepository.findOne(id);
//find the document, modify and update it with save() method.
if(presentInDBStudy != null) {
presentInDBStudy.setTitle(task.getTitle());
presentInDBStudy.setDescription(study.getDescription());
presentInDBStudy.setStart(study.getStart());
presentInDBStudy.setEnd(study.getEnd());
repository.save(presentInDBStudy);
}
else
repository.save(study);
}
}
You will have to use the MongoTemplate.upsert() to achieve this.
You will need to add two more classes: StudyRepositoryCustom which is an interface and a class that extends this interface, say StudyRepositoryImpl
interface StudyRepositoryCustom {
public WriteResult updateStudy(Study study);
}
Update your current StudyRepository to extend this interface
#Repository
public interface StudyRepository extends MongoRepository<Study, String>, StudyRepositoryCustom {
// ... Your code as before
}
And add a class that implements the StudyRepositoryCustom. This is where we will #Autowire our MongoTemplate and provide the implementation for updating a Study or saving it if it does not exist. We use the MongoTemplate.upsert() method.
class StudyRepositoryImpl implements StudyRepositoryCustom {
#Autowired
MongoTemplate mongoTemplate;
public WriteResult updateStudy(Study study) {
Query searchQuery = new Query(Criteria.where("id").is(study.getId());
WriteResult update = mongoTemplate.upsert(searchQuery, Update.update("title", study.getTitle).set("description", study.getDescription()).set(...)), Study.class);
return update;
}
}
Kindly note that StudyRepositoryImpl will automatically be picked up by the Spring Data infrastructure as we've followed the naming convention of extending the core repository interface's name with Impl
Check this example on github, for #Autowire-ing a MongoTemplate and using custom repository as above.
I have not tested the code but it will guide you :-)
You can use upsert functionality for this as described in mongo documentation.
https://docs.mongodb.com/v3.2/reference/method/db.collection.update/
You can update your code to use <S extends T> List<S> save(Iterable<S> entites); to save all the entities. Spring's MongoRepository will take care of all possible cases based on the presence of _id field and its value.
More information here https://docs.mongodb.com/manual/reference/method/db.collection.save/
This will work just fine for basic save operations. You don't have to load the document for update. Just set the id and make sure to include all the fields for update as it updates by replacing the existing document.
Simplified Domain Object:
#Document(collection = "study")
public class Study {
#Id
private String id;
private String name;
private String value;
}
Repository:
public interface StudyRepository extends MongoRepository<Study, String> {}
Imagine you've existing record with _id = 1
Collection state before:
{
"_id" : 1,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "insert"
}
Run all the possible cases:
public void saveStudies() {
List<Study> studies = new ArrayList<Study>();
--Updates the existing record by replacing with the below values.
Study update = new Study();
update.setId(1);
update.setName("saveType");
update.setValue("update");
studies.add(update);
--Inserts a new record.
Study insert = new Study();
insert.setName("saveType");
insert.setValue("insert");
studies.add(insert);
--Upserts a record.
Study upsert = new Study();
upsert.setId(2);
upsert.setName("saveType");
upsert.setValue("upsert");
studies.add(upsert);
studyRepository.save(studies);
}
Collection state after:
{
"_id" : 1,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "update"
}
{
"_id" : 3,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "insert"
}
{
"_id" : 2,
"_class" : "com.mongo.Study",
"name" : "saveType",
"value" : "upsert"
}
Just a quick question about something I've just experienced and I'm still thinking about why:
mongos> db.tickets.count({ "idReferenceList" : { "$in" : [ { "$oid" : "53f1f09f2cdcc8f339e5efa2"} , { "$oid" : "5409ae2e2cdc31c5aa0ce0a5"}]}});
0
mongos> db.tickets.count({ "idReferenceList" : { "$in" : [ ObjectId("53f1f09f2cdcc8f339e5efa2") , ObjectId("5409ae2e2cdc31c5aa0ce0a5")]}});
2
I thought that both $oid and ObjectId spelling formats where exactly the same for MongoDB. Does anyone know why with the first query return 0 results and with the second one is returning 2 (the right answer)?
Furthermore, I'm using Morphia framework which uses MongoDB Java driver to interact with MongoDB. I've realised that there exists a problem by searching with $in operator in ObjectIds arrays over fields that are not _id by executing this lines of code:
List< ObjectId > fParams = new ArrayList< ObjectId >();
fParams.add(...);
Query<Ticket> query = genericDAO.createQuery();
query.field("idReferenceList").in(fParams);
result = genericDAO.find(query).asList();
Thank you very much in advance.
Regards,
Luis Cappa
Both these formats are valid representations of an object id in MongoDB, according to the documentation,
http://docs.mongodb.org/manual/reference/mongodb-extended-json/
and they represented differently in the two modes,
Strict Mode mongo Shell Mode
----------- ----------------
{ "$oid": "<id>" } ObjectId( "<id>" )
So, to query fields which contain objectid, from the shell/console mode, you need to use ObjectId("<id>").
Which is the syntax to be followed in the mongo shell mode.
Hence the query:
db.tickets.count({ "idReferenceList" : { "$in" : [ ObjectId("53f1f09f2cdcc8f339e5efa2") , ObjectId("5409ae2e2cdc31c5aa0ce0a5")]}});
would return you row count.
Now to do it via the Java API,
You need to do it as below:
String[] ids = {"53f1f09f2cdcc8f339e5efa2","5409ae2e2cdc31c5aa0ce0a5"};
ObjectId[] objarray = new ObjectId[ids.length];
for(int i=0;i<ids.length;i++)
{
objarray[i] = new ObjectId(ids[i]);
}
BasicDBObject inQuery = new BasicDBObject("$in", objarray);
BasicDBObject query = new BasicDBObject("idReferenceList", inQuery);
DBCursor cursor = db.collection.find(query);
while(cursor.hasNext())
{
DBObject doc = cursor.next();
// process the doc.
}
I faced the same issue.
I resolved like this way.
db.collection('post').find({ 'postIds': { $elemMatch: { $in:
deletedPosts.map(_post => {ObjectId(_post._id)}) } } })
How to identify the number of elements in the particular embedded document (or) how to find the number of elements in the embedded array?
Award
{
"brand" : [
{
"name" : "multi",
"descr" : "Multpple"
},
{
"name" : "multi",
"descr" : "two"
},
{
"name" : "multi",
"descr" : "three"
}
],
"name" : "Test",
"narname" : "Nar"
}
For Eg: In the above document how to find the number of elements that is inside the embedded array BRAND using Spring Data.?
Any pointers would be greatly appreciated !
I don't think there is a method which can get the answer directly.
You can use aggregate to implement it. For example, if you want to get the count of elements in array brand in a specific document, this way should be available (run on mongo shell):
db.Award.aggregate({$match:{_id:id}}, {$unwind:"$brand"}, {$group:{_id:"$_id", count:{$sum:1}}});
count is the result you want.
Then implement that using spring-data-mongodb syntax.
-------------- APPENDED ---------------------
// You can find the relative aggregation method from MongoTemplate.java file to handle your requirements.
// For exmaple:
// public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType)
// The version is around spring-data-mongodb-1.5.0 or higher.
// Below I call the mongo-java-driver directly because I find it needs some time to learn it from spring-data-mongodb. :)
protected int getArraySize(Object id, String collName) {
// Attention: make sure id is in the correct data type because the following statement would not convert it automatically.
// Issue codes according to this command line:
// db.Award.aggregate({$match:{_id:id}}, {$unwind:"$brand"}, {$group:{_id:"$_id", count:{$sum:1}}});
DBObject match = BasicDBObjectBuilder.start().push("$match").append("_id", id).get();
DBObject unwind = new BasicDBObject("$unwind", "$brand");
DBObject group = BasicDBObjectBuilder.start().push("$group").append("_id", "$_id").push("count").append("$sum", 1).get();
List<DBObject> pipeline = Arrays.asList(match, unwind, group);
// This aggregate method is supported in higher version of mongo-java-driver, here I use is 2.12.3
AggregationOutput aggr = this.mongoTemplate.getCollection(collName).aggregate(pipeline);
for (DBObject dbo : aggr.results()) {
Object count = dbo.get("count");
if (count instanceof Number) {
return ((Number)count).intValue();
}
}
return 0;
}
My app is dynamically generating a mongo query via a QueryBuilder. I'm trying to make it so that the passed text can be compared against several fields in my collection. I need to iterate through data in order to know what to add to the $OR ( 'text_to_compare' vs title field, 'text_to_compare vs body field), so I've been trying to store each section of the $OR clause in a BasicDBList as follows:
BasicDBObject orsDBObject = new BasicDBObject();
BasicDBList orsList = new BasicDBList();
for( String dbFieldNameInstance: listDBFieldName ){
QueryBuilder innerQB = new QueryBuilder();
innerQB.put(dbFieldNameInstance).is(obj.getValue()); //using this inner QueryBuilder to assist in creating the document
orsList.add(innerQB.get());
}
orsDBObject.putAll(orsList); //put each of the BasicDBList items into a BasicDBObject
qb.or(orsDBObject); //finally, add the entire $OR clause to my bigger querybuilder
System.out.println("qb: " + qb.get());
It seems like I might be jumping through more conversions than are necessary. It isn't working either. The output from the above ends up looking like this:
qb: { "$or" : [ { "0" : { "primary_industries_names" : "electrical"} , "1" : { "primary_industries_aliases" : "electrical"} , "2" : { "secondary_industries_names" : "electrical"} , "3" : { "secondary_industries_aliases" : "electrical"} , "4" : { "product_experience_names" : "electrical"} , "5" : { "product_experience_aliases" : "electrical"}}]}
For the most part it looks right to me, however I don't think the "0": should be appearing in there. Any advice would be greatly appreciated.
You kind of have that the wrong way around. Not re-writing your code, but you should get the idea:
// Before the loop
QueryBuilder qb = new QueryBuilder();
// And loop things like this
for ( ) {
qb.or(new BasicDBObject("this","that"));
}
// After loop
System.out.println(qb.get());
If you are trying to nest several conditions then you seem to be missing the $and operator. Consider the following:
QueryBuilder qb = new QueryBuilder();
for (int x=0; x<2;x++) {
qb.or(new BasicDBObject("this","that"));
}
QueryBuilder qb2 = new QueryBuilder();
qb2.and(qb.get());
QueryBuilder qb3 = new QueryBuilder();
for (int x=0; x<2;x++) {
qb3.or(new BasicDBObject("something", "else"));
}
qb2.and(qb3.get());
System.out.println(qb2.get());