How to execute MongoDB findAndModify query in MongoCollection Java driver 3? - java

MongoDB 2.5 driver have DBCollection.findAndModify() method for this, but MongoCollection misses this method. After some search, I found that findOneAndUpdate() now has the same role.
But this method has different signature, don't understand how to use it. Here is command I want to execute
db.COL1.findAndModify({
query: { id: 2 },
update: {
$setOnInsert: { date: new Date(), reptype: 'EOD' }
},
new: true, // return new doc if one is upserted
upsert: true // insert the document if it does not exist
})
Documentation for findOneAndUpdate method states that
Returns:
the document that was updated. Depending on the value of the returnOriginal property, this will either be the document as it was before the update or as it is after the update.
but cannot find anything about this returnOriginal property. Anyone knows how to set it correctly?

A Java equivalent of your query should go roughly like this:
Document query = new Document("id", 2);
Document setOnInsert = new Document();
setOnInsert.put("date", new Date());
setOnInsert.put("reptype", "EOD");
Document update = new Document("$setOnInsert", setOnInsert);
FindOneAndUpdateOptions options = new FindOneAndUpdateOptions();
options.returnDocument(ReturnDocument.AFTER);
options.upsert(true);
db.getCollection("COL1").findOneAndUpdate(query, update, options);
Regarding the returnOriginal property - you're right - there is no such thing. The javadoc is irrelevant in this place. However, there is a returnDocument property in FindOneAndUpdateOptions. You can set it to ReturnDocument.AFTER or ReturnDocument.BEFORE which is equivalent to new: true/false.

Related

Setting last modification timestamp when using insertOne and findOneAndUpdate

I need all my inserted/updated documents in MongoDb to have an auto-updated currentDate
So let's assume I have the following Json shipment object (which I'm getting from a 3rd party restful API) :
String jsonString = {"tracking_number": "123", "deliveryAddress": { "street_line_1": "12 8th St.", "city": "NY", "state": "NY" }, "cutomers": [ { "firstName": "John", "email": "john#gmail.com" }, { "firstName": "Alex", "email": "alex#gmail.com" } ] }
Problem #1, I need to insert the object into the DB and set "currentDate", but insertOne does not work for me:
MongoClient mongo = new MongoClient(mongodb_host, mongodb_port);
MongoDatabase db = mongo.getDatabase("Test");
MongoCollection<Document> collection = db.getCollection("InsertOneExample");
Document doc = Document.parse(jsonString);
doc.append("lastModifiedTs", new BSONTimestamp());
collection.insertOne(doc);
System.out.println(doc);
This one does not populate "lastModifiedTs" as you can see below
Document{{tracking_number=123, deliveryAddress=Document{{street_line_1=12 8th St., city=NY, state=NY}}, cutomers=[Document{{firstName=John, email=john#gmail.com}}, Document{{firstName=Alex, email=alex#gmail.com}}], lastModifiedTs=TS time:null inc:0, _id=5a6b88a66cafd010f1f2cffd}}
Problem #2
If I'm getting an update on my shipment, the tracking number is the same, but all the other fields may change.
The following code crashes:
FindOneAndUpdateOptions options = new FindOneAndUpdateOptions();
options.returnDocument(ReturnDocument.AFTER);
options.upsert(true);
Bson update = Updates.combine(Document.parse(jsonString), Updates.currentTimestamp("lastModifiedTs"));
Document query = new Document("tracking_number", "123");
Document result = collection.findOneAndUpdate(query, update, options);
With the exception: "Invalid BSON field name equipmentShipmentAddress"
So it looks like I cannot just to put the entire updated document into the "update"
If I set update just to Updates.currentTimestamp("lastModifiedTs"), the code will update just the field "lastModifiedTs", but I need it to modify all the fields.
If I set the query to be the new object, then due to my "upsert" setting, it'll add the new document without replacing the old one.
Notes: needless to say, I can perform several operations: (1) insert the object, get the "_id" field, (2) update the "lastModifiedTs" field (3) read the object by "_id" and get the updated "lastModifiedTs" value, but it's three operation where I expect to be able to achieve everything with a single operation
How can I achieve my goal elegantly?
Thanks
Solution to Problem #1 - Insert new Date() to provide new datetime.
Document doc = Document.parse(jsonString);
doc.append("lastModifiedTs", new Date());
collection.insertOne(doc);
Solution to Problem #2 - Use findOneAndReplace
FindOneAndReplaceOptions options = new FindOneAndReplaceOptions();
options.returnDocument(ReturnDocument.AFTER);
Document replace = Document.parse(jsonString);
replace.append("lastModifiedTs", new Date());
Document query = new Document("tracking_number", "123");
Document result = collection.findOneAndReplace(query, replace, options);

MongoDB Java Async Driver replaceOne doesn't seem to work

I am trying to update a document with MongoDB Async Java Driver and my code is below,
// jsonString is a string of "{_id=5715e426ed3522391f106e68, name=Alex}
final Document document = Document.parse(jsonString);
Document newDocument = document.append("status", "processing");
mongoDbCollection.replaceOne(document, newDocument, (updateResult, throwable) -> {
if (updateResult != null) {
log.info("UPDATED DOC ::::::>>> " + newDocument.toJson());
log.info("UPDATED RESULT ::::>> "+updateResult.toString());
} else {
throwable.printStackTrace();
log.error(throwable.getMessage());
}
});
As per the logging, I do see the updated document as below,
INFO: UPDATED DOC ::::::>>> { "_id" : { "$oid" : "5715e426ed3522391f106e68" }, "status":"processing"}
INFO: UPDATED RESULT ::::>> AcknowledgedUpdateResult{matchedCount=0, modifiedCount=0, upsertedId=null}
But when I see the collection via Robmongo I do not see the updated document and it still shows the old document. I have double checked I am looking in the same collection and there aren't any exceptions. Am I doing something wrong here?
The problem here is this line:
Document newDocument = document.append("status", "processing");
Where you "thought" you were just assigning a "new document copy", but actually this "also" modifies the document object to append the field.
As such, the query does not match, just as indicated in your output:
{matchedCount=0, modifiedCount=0, upsertedId=null}
// ^ Right here! See 0 matched
So what you want is a "clone". It's not straightforward with a Document, but can be done with this slightly "hacky" method:
Document document = Document.parse(jsonString);
Document newDocument = Document.parse(document.toJson()).append("status","processing");
System.out.println(newDocument);
System.out.println(document);
Now you will see that newDocument contains the addition, whilst document remains unaltered, which is not the case with your current code, and why the query does not match anything to update.

Java driver: how to get the objectId of an updated object with Mongodb's updateFirst method

I'm trying to get the objectId of an object that I have updated - this is my java code using the java driver:
Query query = new Query();
query.addCriteria(Criteria.where("color").is("pink"));
Update update = new Update();
update.set("name", name);
WriteResult writeResult = mongoTemplate.updateFirst(query, update, Colors.class);
Log.e("object id", writeResult.getUpsertedId().toString());
The log message returns null. I'm using a mongo server 3.0 on mongolab as I'm on the free tier so it shouldn't return null. My mongo shell is also:
MongoDB shell version: 3.0.7
Is there an easy way to return the object ID for the doc that I have just updated? What is the point of the method getUpsertedId() if I cannot return the upsertedId?
To do what I want, I currently have to issue two queries which is highly cumbersome:
//1st query - updating the object first
Query query = new Query();
query.addCriteria(Criteria.where("color").is("pink"));
Update update = new Update();
update.set("name", name);
WriteResult writeResult = mongoTemplate.updateFirst(query, update, Colors.class);
//2nd query - find the object so that I can get its objectid
Query queryColor = new Query();
queryColor.addCriteria(Criteria.where("color").is("pink"));
queryColor.addCriteria(Criteria.where("name").is(name));
Color color = mongoTemplate.findOne(queryColor, Color.class);
Log.e("ColorId", color.getId());
As per David's answer, I even tried his suggestion to rather use upsert on the template, so I changed the code to the below and it still does not work:
Query query = new Query();
query.addCriteria(Criteria.where("color").is("pink"));
Update update = new Update();
update.set("name", name);
WriteResult writeResult = mongoTemplate.upsert(query, update, Colors.class);
Log.e("object id", writeResult.getUpsertedId().toString());
Simon, I think its possible to achieve in one query. What you need is a different method called findAndModify().
In java driver for mongoDB, it has a method called findOneAndUpdate(filter, update, options).
This method returns the document that was updated. Depending on the options you specified for the method, this will either be the document as it was before the update or as it is after the update. If no documents matched the query filter, then null will be returned. Its not required to pass options, in that case it will return the document that was updated before update operation was applied.
A quick look at the mongoTemplate java driver docs here: http://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/FindAndModifyOptions.html tells me that you can use the method call:
public <T> T findAndModify(Query query,
Update update,
FindAndModifyOptions options,
Class<T> entityClass)
You can also change the FindAndModifyOptions class to take on an 'upsert' if the item was not found in the query.If it is found, the object will just be modified.
Upsert only applies if both
The update options had upsert on
A new document was actually created.
Your query neither has upsert enabled, nor creates a new document. Therefore it makes perfect sense that the getUpsertedId() returns null here.
Unfortunately it is not possible to get what you want in a single call with the current API; you need to split it into two calls. This is further indicated by the Mongo shell API for WriteResults:
The _id of the document inserted by an upsert. Returned only if an
upsert results in an insert.
This is an example to do this with findOneAndUpdate(filter,update,options) in Scala:
val findOneAndUpdateOptions = new FindOneAndUpdateOptions
findOneAndUpdateOptions.returnDocument(ReturnDocument.AFTER)
val filter = Document.parse("{\"idContrato\":\"12345\"}")
val update = Document.parse("{ $set: {\"descripcion\": \"New Description\" } }")
val response = collection.findOneAndUpdate(filter,update,findOneAndUpdateOptions)
println(response)

Tailable Cursor example in Java using 3.0 driver?

Can someone please provide a complete tailable cursor example in Java? I am using the 3.0 driver and all examples appear to be 2.x. I have only mongo-java-driver-3.0.0.jar in my classpath. I want to get all documents as they are inserted in my capped collection.
//this does not work...
MongoCollection<BasicDBObject> col = database.getCollection(colName, BasicDBObject.class);
DBCursor cur = col.find().sort(new BasicDBObject("$natural", 1))
.addOption(Bytes.QUERYOPTION_TAILABLE)
.addOption(Bytes.QUERYOPTION_AWAITDATA);
// And this does not work...
BasicDBObjectBuilder builder = BasicDBObjectBuilder.start();
builder.add("messageType","STATUS_REQUEST");
DBObject searchQuery = builder.get();
DBObject sortBy = BasicDBObjectBuilder.start("$natural", 1).get();
BasicDBObjectBuilder builderForFields = BasicDBObjectBuilder.start();
DBObject fields = builderForFields.get();
DBCursor cursor = new DBCursor(col, searchQuery, fields, ReadPreference.primary() );
cursor.sort(sortBy);
cursor.addOption(Bytes.QUERYOPTION_AWAITDATA);
cursor.addOption(Bytes.QUERYOPTION_TAILABLE);
//this does work but only returns the messageNumber field. I need the doc.
MongoCursor<Long> c = database.getCollection(colName).distinct("messageNumber", Long.class).iterator();
I see that the MongoCursor interface was added in 3.0. What is that for and does it replace DBCursor?
Thanks a lot
A bit late to the party, but in case you still need help:
find(query).projection(fields).cursorType(CursorType.TailableAwait).iterator();
That code applies to the MongoCollection class.
CursorType is an enum and it has the following values:
Tailable
TailableAwait
Corresponding to the old DBCursor addOption Bytes types:
Bytes.QUERYOPTION_TAILABLE
Bytes.QUERYOPTION_AWAITDATA
I hope that helps.
This is what you might be looking for - EVENT Streaming in MongoDB 3.0.* using new api i.e. 3.0.2
Document query = new Document(); \\here use { indexedField: { $gt: <lastvalue> } index is not used(except for auto deleting documents) but created in capped collection
Document projection = new Document();
MongoCursor<Document> cursor= mongocollection.find(query).projection(projection).cursorType(CursorType.TailableAwait).iterator(); //add .noCursorTimeout(true) here to avoid timeout if you are working on big data
while (cursor.hasNext()){
Document doc = cursor.next();
//do what you want with doc
}
This way mongo cursor will check for new entries in capped collection
In your last line, replace .distinct("messageNumber", Long.class) with .find().
distinct(String fieldName, Class<TResult> resultClass) returns only the unique values of the one field you request.
find() returns all documents of the collection with all of their fields.

MongoDB - How to find and append / find and upsert both document and fields

In Mongo, is there a built-in way to update a document and instead of replacing all contents of the query document, to update those nodes which are the same and append those which do not exist in the original document.
For example, imagine I insert the following document into my collection:
{
"name" : "Goku",
"level" : 9000
}
Now, at some later point, I wish to update my existing document with the following document I received:
{
"name" : "Goku",
"son" : "Gohan"
}
Ideally, I would like a way to perform an update and produce the following document:
{
"name" : "Goku",
"level" : 9000,
"son" : "Gohan"
}
The standard case is to overwrite the existing document with the new document (as it should be). However, is there a built-in or clever way to achieve the result above without first finding the first document, appending onto it, and then performing an update?
Thanks.
-- EDIT --
#pennstatephil has the correct answer below. Just in case anyone's is helped by this, here's an implementation of this example in Java as of driver version 2.12.0:
String json = "{'name' : 'Goku', 'level' : 9000 }";
DBObject document = (DBObject) JSON.parse(json);
BasicDBObject update = new BasicDBObject("$set", document);
BasicDBObject query = new BasicDBObject().append("name", document.get("name"));
collection.findAndModify(query, null, null, false, update, false, true);
json = "{'name' : 'Goku', 'son' : 'Gohan'}";
document = (DBObject) JSON.parse(json);
update = new BasicDBObject("$set", document);
query = new BasicDBObject().append("name", document.get("name"));
collection.findAndModify(query, null, null, false, update, false, true);
I believe findAndModify and $set (on the update clause) is what you're looking for.

Categories

Resources