How to set value in nested BasicDBObject with Java MongoDB driver - java

I have a mongo document that I am trying to update before insert in mongodb.
I have to put theses 3 keys
document._parentId = ObjectId()
document.aDictionnary.actionId = Integer
document.aDictionnary.bDictionnary.id = Integer
I have tried a few combinaison, but can't make it work.
Here is my current code
myClass.getDocument().append( "$set", new BasicDBObject().append("_parentId", myClass.getDocument.getId() ) );
myClass.getDocument().append( "$set", new BasicDBObject().append("aDictionnary", new BasicDBObject().append("actionId", actionToAttachId ) ) );
if( null == myClass.getSiteId() )
{
myClass.getDocument().append( "$set", new BasicDBObject().append("aDictionnary", new BasicDBObject().append("bDictionnary", new BasicDBObject().append( "id", actionToAttach.getSiteId() ))));
}
I don't want to directly update my document in database, reason is I keep all the history so each entry is a new insert.
Application doesn't crash, but fail on insert because of a wrong "append" syntax
Also I think it's unpleasing to write that kind of code because of the nested basicdbobject.append syntax, is there another way to do so ?
Here is the stack trace
163530 [http-8080-8] ERROR com.myapp.persistance.mystuff.MyClassMongo - java.lang.IllegalArgumentException: fields stored in the db can't start with '$' (Bad Key: '$set')

Have you tried the $push operator?
Example:
db.students.update({ _id: 1 },{ $push: { scores: 89 }})

In your example:
document._parentId = ObjectId()
document.aDictionnary.actionId = Integer
document.aDictionnary.bDictionnary.id = Integer
Essentially that equates to:
{_parentId: ObjectId(), aDictionary: {actionId: actionId,
bDictionnary: {id: id}}
So there are 3 documents - the toplevel all the way down to the nested bDictionnary. Each one of these is a DBObject so you'd need to build up the DBObject and save it as appropriate - here is some untested pseudo code:
DBObject myDocument = new BasicDBObject();
myDocument.put("_parentId", ObjectId())
DBObject aDictionary = new BasicDBObject("actionId", actionId)
.append("bDictionary", new BasicDBObject("id", id))
myDocument.put("aDictionary", aDictionary)
db.save(myDocument)

Related

Spring Data MongoDB and Bulk Update

I am using Spring Data MongoDB and would like to perform a Bulk Update just like the one described here: http://docs.mongodb.org/manual/reference/method/Bulk.find.update/#Bulk.find.update
When using regular driver it looks like this:
The following example initializes a Bulk() operations builder for the items collection, and adds various multi update operations to the list of operations.
var bulk = db.items.initializeUnorderedBulkOp();
bulk.find( { status: "D" } ).update( { $set: { status: "I", points: "0" } } );
bulk.find( { item: null } ).update( { $set: { item: "TBD" } } );
bulk.execute()
Is there any way to achieve similar result with Spring Data MongoDB ?
Bulk updates are supported from spring-data-mongodb 1.9.0.RELEASE. Here is a sample:
BulkOperations ops = template.bulkOps(BulkMode.UNORDERED, Match.class);
for (User user : users) {
Update update = new Update();
...
ops.updateOne(query(where("id").is(user.getId())), update);
}
ops.execute();
You can use this as long as the driver is current and the server you are talking to is at least MongoDB, which is required for bulk operations. Don't believe there is anything directly in spring data right now (and much the same for other higher level driver abstractions), but you can of course access the native driver collection object that implements the access to the Bulk API:
DBCollection collection = mongoOperation.getCollection("collection");
BulkWriteOperation bulk = collection.initializeOrderedBulkOperation();
bulk.find(new BasicDBObject("status","D"))
.update(new BasicDBObject(
new BasicDBObject(
"$set",new BasicDBObject(
"status", "I"
).append(
"points", 0
)
)
));
bulk.find(new BasicDBObject("item",null))
.update(new BasicDBObject(
new BasicDBObject(
"$set", new BasicDBObject("item","TBD")
)
));
BulkWriteResult writeResult = bulk.execute();
System.out.println(writeResult);
You can either fill in the DBObject types required by defining them, or use the builders supplied in the spring mongo library which should all support "extracting" the DBObject that they build.
public <T> void bulkUpdate(String collectionName, List<T> documents, Class<T> tClass) {
BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, tClass, collectionName);
for (T document : documents) {
Document doc = new Document();
mongoTemplate.getConverter().write(document, doc);
org.springframework.data.mongodb.core.query.Query query = new org.springframework
.data.mongodb.core.query.Query(Criteria.where(UNDERSCORE_ID).is(doc.get(UNDERSCORE_ID)));
Document updateDoc = new Document();
updateDoc.append("$set", doc);
Update update = Update.fromDocument(updateDoc, UNDERSCORE_ID);
bulkOps.upsert(query, update);
}
bulkOps.execute();
}
Spring Mongo template is used to perform the update. The above code will work if you provide the _id field in the list of documents.

using $$ROOT inside Spring Data Mongodb for retrieving whole document

using mongodb shell, I am able to perform an aggregation query that retrieves the whole document.
In order to do that I use the $$ROOT variable.
db.reservations.aggregate([
{ $match : { hotelCode : "0360" } },
{ $sort : { confirmationNumber : -1 , timestamp: -1 } },
{ $group : {
_id : "$confirmationNumber",
timestamp :{$first : "$timestamp"},
fullDocument :{$first : "$$ROOT"}
}}
])
It retrieves objects whose content is confirmationNumber, timestamp, fullDocument.
fullDocument is the whole document.
I am wondering if it is possible to do the same with Spring-Data and the aggregation framework.
My java code is:
TypedAggregation<ReservationImage> aggregation = newAggregation(
ReservationImage.class,
match(where("hotelCode").is(hotelCode)),
sort(Direction.DESC,"confirmationNumber","timestamp"),
group("confirmationNumber").
first("timestamp").as("timestamp").
first("$$ROOT").as("reservationImage"));
List<myClass> items = mongoTemplate.aggregate(
aggregation,
myClass.class).getMappedResults();
the error is :
org.springframework.data.mapping.PropertyReferenceException: No property $$ found for type myClass
Do you have any ideas?
Thanks.
We created https://jira.spring.io/browse/DATAMONGO-954 to track the support for accessing System Variables from MongoDB Pipeline expressions.
Once that is in place, you should be able to write:
Aggregation agg = newAggregation( //
match(where("hotelCode").is("0360")), //
sort(Direction.DESC, "confirmationNumber", "timestamp"), //
group("confirmationNumber") //
.first("timestamp").as("timestamp") //
.first(Aggregation.ROOT).as("reservationImage") //
);
I've seen this sort of thing before and it is not just limited to variable names such as $$ROOT. Spring data has it's own ideas about how to map "properties" of the document in the pipeline. Another common problem is simply projecting a new or calculated field that essentially has a new "property" name to it that does not get recognized.
Probably the best approach is to "step down" from using the helper classes and methods and construct the pipeline as BSON documents. You can even get the underlying collection object and the raw output as a BSON document, yet still cast to your typed List at the end.
Mileage may vary to your actual approach,but essentially:
DBObject match = new BasicDBObject(
"$match", new BasicDBObject(
"hotelCode", "0360"
)
);
DBObject sort = new BasicDBObject(
"$sort", new BasicDBObject(
"cofirmationNumber", -1
).append("timestamp", -1)
);
DBObject group = new BasicDBObject(
"$group", new BasicDBObject(
"_id", "confirmationNumber"
).append(
"timestamp", new BasicDBObject(
"$first", "$timestamp"
)
).append(
"reservationImage", new BasicDBObject(
"$first", "$$ROOT"
)
)
);
List<DBObject> pipeline = Arrays.asList(match,sort,group);
DBCollection collection = mongoOperation.getCollection("collection");
DBObject rawoutput = (DBObject)collection.aggregate(pipeline);
List<myClass> items = new AggregationResults(List<myClass>, rawoutput).getMappedResults();
The main thing is moving away from the helpers that are getting in the way and constructing the pipeline as it should be free of the imposed restrictions.

How to represent the following query(mongodb) in java

I have a JSON in MongoDB with the following structure:
{
id:"_234234",
"stationId":"ALM",
"storageData": {
}
}
To retrieve JSON with storageData equal to null, in MongoDB I query as:
db.collection.find({"storageData":{"$gt" : {}}})
It gives me list of JSON bodies with empty storageData. So how do I represent that in Java
BasicDBObject obj=new BasicDDBObject("storageData", new BasicDBObject("$gt",{}));
collection.find(obj);
I am getting an error near BasicDBObject("$gt",{}))...
How do I represent ("$gt",{}) in Java??
First understand that null is a valid value. This would be valid:
{
id:"_234234",
StationId:"ALM",
StorageData: null
}
and retrieving the document, asking for storagedata which is null would retrieve the doc with the id _234234.
If what you need is to check which documents DON'T have the key "storagedata" then use the $exist keyword or construct the query in this way:
db.yourcollection.find("this.storagedata == null")
I would do it via query, and not in Java because it would alleviate cpu time and memory.
All you want to to here is represent an empty object:
BasicDBObject query = new BasicDBObject(
"storageData", new BasicDBObject(
"$gt",new BasicDBObject()
)
);
Which of course produces the query:
{ "storageData" : { "$gt" : { } } }
So that's it, just call BasicDBObject without any arguments.

MongoDB Update Multiple Documents based on ObjectID (_id)

I'm using the Java driver with MongoDB. I have a List of document id's in a collection. I want to update a single field in every document that has an "_id" equal to one of the document id's in my List. In the below example, I tried something like this:
List<ObjectID> list = new ArrayList<ObjectID>();
list.append(new ObjectId("123"));
list.append(new ObjectId("456"));
list.append(new ObjectId("789"));
column.updateMulti(new BasicDBObject("_id", list),new BasicDBObject("$set",new BasicDBObject("field",59)));
My intentions are to update the documents with _id=123, _id=456 and _id=789, setting their "field" attribute to 59.
Am I going about this the right way?
I believe you need to make a couple changes:
BasicDBList list = new BasicDBList();
list.add( new ObjectId("123") );
// Add the rest...
DBObject inStatement = new BasicDBObject( "$in", list );
column.updateMulti( new BasicDBObject( "_id", inStatement ), new BasicDBObject( "$set", new BasicDBObject( "field", 59 ) );
Otherwise, with your current query, you're doing an equality comparison of the _id property against a list of _ids - not actually using the $in operator.

Updating fields in embedded documents with MongoDB Java Driver?

I'm trying to perform an update on an embedded document in MongoDB with the Java driver but receive an IllegalArgumentException that states "fields stored in the db can't have . in them"
My document has the structure:
{
"_id" : ObjectId("5155d102a47d7b00b7e4bed2"),
"foo" : {
"bar" : {
"name" : "Now"
}
}
}
and I want to perform an update like this
var query = {_id:ObjectId("5155d102a47d7b00b7e4bed2")};
var update = {"foo.bar.time":new Date()};
var withSet = {$set:update};
db.samples.update(query,withSet);
which from the console correctly modifies the document
{
"_id" : ObjectId("5155d102a47d7b00b7e4bed2"),
"foo" : {
"bar" : {
"name" : "Now",
"time" : ISODate("2013-03-29T18:02:51.591Z")
}
}
}
Trying to do the same in Java I have not been successful. I've tried this:
BasicDBObject query = new BasicDBObject("_id", new ObjectId("5155d102a47d7b00b7e4bed2"));
BasicDBObject time = new BasicDBObject("time", new Date());
BasicDBObject bar = new BasicDBObject("bar", time);
BasicDBObject foo = new BasicDBObject("foo", bar);
BasicDBObject withSet = new BasicDBObject("$set", foo);
samples.update(query, withSet);
But it clobbers the embedded bar object, destroying name.
I've also tried:
BasicDBObject foo = new BasicDBObject();
foo.append("foo.bar.time", new Date());
samples.update(query, foo)
But receive an IllegalArgumentException.
I've seen other answers chosen on Stack Overflow that include this dot notation. Is there something I'm missing? What is the proper way to update a field in an embedded document?
In your last example the $set part is missing, maybe this causes the problem. Try
samples.update(
query,
new BasicDBObject(
"$set",
new BasicDBObject("foo.bar.time", new Date())
));
instead.

Categories

Resources