what is the idiomatic way to upsert a document using version 3 of the mongodb java driver (specifically v3.0.1)?
We have a collection for sessions and when a new session gets created or modified, we want to upsert it in one operation - rather than having to query if a document exists yet and then either inserting or replacing.
Our old upsertion code used the scala driver casbah 2.7.3. It looked like:
import com.mongodb.casbah.MongoCollection
import com.mongdb.DBObject
val sessionCollection: MongoCollection = ...
val sessionKey: String = ...
val sessionDocument: DBObject = ... // Either create a new one, or find and modify an existing one
sessionCollection.update(
"_id" -> sessionKey,
sessionDocument
upsert = true
)
In our current project we're just using the plain java 3.0.1 driver and we're using BsonDocument instead of DBObject to make it more typsafe. I tried to replace the above with something like:
import com.mongodb.client.MongoCollection
val sessionCollection: MongoCollection = ...
val sessionKey: String = ...
val sessionDocument: BsonDocument = // Either create a new one, or find and modify an existing one
val updateOptions = new UpdateOptions
updateOptions.upsert(true)
sessionCollection.updateOne(
"_id" -> new BsonString(sessionKey),
sessionDocument,
updateOptions
)
This throws the error "java.lang.IllegalArgumentException: Invalid BSON field name ...". The error is covered in this question but the op in that question wasn't trying to upsert in one operation - they were using context to decide whether to replace/update/insert etc...
I'm happy with code samples in scala or java.
Thanks!
In the Mongo Java Driver 3.0 series we added a new Crud API which is more explicit and therefore beginner friendly. This initiative has been rolled out over a number of MongoDB Drivers but it does contain some changes compared to the older API.
As you are not updating an existing document using an update operator, the updateOne method is not appropriate.
The operation you describe is a replaceOne operation and can be run like so:
sessionCollection.replaceOne(
"_id" -> new BsonString(sessionKey),
sessionDocument,
(new UpdateOptions()).upsert(true)
)
Related
I am working on this legacy application (7 years old). I have many methods that do the same thing that I am trying to upgrade to a newer MongoDB Java driver, but it won't compile.
#Override
public void saveOrUpdatePrinter(Document printer) {
printer.put(PRINTER_COLUMNS.updateDate,new Date());
MongoCollection<Document> collection = mongoTemplate.getCollection("PRINTERS");
printer.remove("_id");
Document query = new Document().append(PRINTER_COLUMNS.internal_id, printer.get(PRINTER_COLUMNS.internal_id));
WriteResult result = collection.update(query, printer, true, false);
logger.debug("saveOrUpdatePrinter updeded records: " + result.getN());
}//
The error is:
The method update(Document, Document, boolean, boolean) is undefined
for the type MongoCollection<Document>
Why was this removed?
printer.remove("_id");
Also I would like to know how to do either update or save on the document in one go?
And what will be the proper way to update a single document in the new (MongoDB Java driver 4.7.0)?
Reading this code a little more seems like it was an attempt to do UPSERT operation (update or insert).
I will try to answer your questions.
Q : How to do either Update or Save on the Document in one go?
-> MongoDB's update method updates the values in the existing document whereas the save method replaces the existing document with the document passed. Nothing happens in one go.
update method only updates which are specific fields which are modified by comparing the fields from the modified document with the original document whereas the save method updates/replaces the values of all the fields of an original document by the taking values from the modified document and setting the values into the original document.
Q : What will be the proper way to update a single document in the new (Mongo Java driver 4.7.0)
-> You should be using updateOne(query, updates, options) to update a single document on a MongoCollection object.
From updateOne docs :
The method accepts a filter that matches the document you want to
update and an update statement that instructs the driver how to change
the matching document. The updateOne() method only updates the first
document that matches the filter.
To perform an update with the updateOne() method, you must pass a
query filter and an update document. The query filter specifies the
criteria for which document to perform the update on and the update
document provides instructions on what changes to make to it.
You can optionally pass an instance of UpdateOptions to the
updateOne() method in order to specify the method's behavior. For
example, if you set the upsert field of the UpdateOptions object to
true, the operation inserts a new document from the fields in both the
query and update document if no documents match the query filter.
Q : Is it seems like it was an attempt to do UPSERT operation (Update or Insert) ?
-> Yes, it's an upsert operation.
Q : Why the code is trying to remove _id from document ?
-> The update method will update the document if the document was found by internal_id. If the document was not found and also if there is no _id field in the document, then the mongoshell will consider it as a new document and will invoke insert method internally via the update method to insert the document. For the insertion to happen, that's why it was removed from document.
Just update the code to this.
#Override
public void saveOrUpdatePrinter(Document printer) {
MongoCollection<Document> collection = mongoTemplate.getCollection("PRINTERS");
Document query = new Document().append(PRINTER_COLUMNS.internal_id, printer.get(PRINTER_COLUMNS.internal_id));
UpdateOptions options = new UpdateOptions().upsert(true);
printer.put(PRINTER_COLUMNS.updateDate,new Date());
UpdateResult result = collection.updateOne(query, printer, options);
logger.debug("saveOrUpdatePrinter updated records: " + result.getModifiedCount());
}
You can update a document using the MongoCollection#updateOne() method
An example would be:
collection.updateOne(Filters.eq("_id", new ObjectId("1234")), Updates.set("date", new Date());
I am using mongodb 3.4 and I want to get the last inserted document id. I have searched all and I found out below code can be used if I used a BasicDBObject.
BasicDBObject docs = new BasicDBObject(doc);
collection.insertOne(docs);
ID = (ObjectId)doc.get( "_id" );
But the problem is am using Document type not BasicDBObject so I tried to get it as like this, doc.getObjectId();. But it asks a parameter which I actually I want, So does anyone know how to get it?
EDIT
This is the I am inserting it to mongo db.
Document doc = new Document("jarFileName", jarDataObj.getJarFileName())
.append("directory", jarDataObj.getPathData())
.append("version", jarDataObj.getVersion())
.append("artifactID", jarDataObj.getArtifactId())
.append("groupID", jarDataObj.getGroupId());
If I use doc.toJson() it shows me whole document. is there a way to extract only _id?
This gives me only the value i want it like the objectkey, So I can use it as reference key.
collection.insertOne(doc);
jarID = doc.get( "_id" );
System.out.println(jarID); //59a4db1a6812d7430c3ef2a5
Based on ObjectId Javadoc, you can simply instantiate an ObjectId from a 24 byte Hex string, which is what 59a4db1a6812d7430c3ef2a5 is if you use UTF-8 encoding. Why don't you just do new ObjectId("59a4db1a6812d7430c3ef2a5"), or new ObjectId("59a4db1a6812d7430c3ef2a5".getBytes(StandardCharsets.UTF_8))? Although, I'd say that exposing ObjectId outside the layer that integrates with Mongo is a design flaw.
I'm trying to replace (not update with $set) documents in MongoDB using mongo-hadoop in Spark (mongo-hadoop-core-1.4.2.jar & mongo-java-driver-3.2.1.jar) :
BasicDBObject query = new BasicDBObject();
query.append("_id", 6972);
BasicDBObject update = new BasicDBObject();
update.append("_id", 6988);
update.append("f1", "ACTIVE_USER");
Then I'm writing something like this :
new MongoUpdateWritable(query, update, false, true);
But this is failing with :
Caused by: java.lang.IllegalArgumentException: Invalid BSON field name f1
I can do :
new MongoUpdateWritable(query, new BasicDBObject("$set", update), false, true);
But I want to replace the whole document.
Update:
mongo-hadoop added support for replacing documents in 2.0.0-rc0. From then on, to replace a document entirely, you'll have to use
new MongoUpdateWritable(query, update, false/*or true*/, false, true)
Note: you can't use replacing and multiUpdate = true together, and replacing also doesn't change the _id value - see Replace a Document Entirely:
The update() method does not replace the _id value.
...
update() cannot update multiple documents.
Looking at the code, that doesn't seem to be possible: MongoOutputCommitter always issues an update command (that can only contain $operators) - see MongoOutputCommitter.java line 155 and below. They use BulkUpdateRequestBuilder's update / updateOne methods, but to issue a replace command they need to use replaceOne method.
So, no such feature. Maybe you'll make a pull request :)
I've got a question about $currentDate
What is the best way to insert a document in mongo db so that it contains the "server time" (like ''now()'' in some RDBMSs) using the Java Driver?
For example, lest say I have a document like:
{
name : "John",
birthday : <$currentDate_goes_here>
}
What I want is to insert the document so that the evaluation of the date would be done by mongo server at the time of insertion on the server side.
This is critical because our servers might not be totally synchronized and there is a need to have the time we can rely on (for example the time on mongo server).
I'm using a standard java driver for mongo, so any code snippet in Java will be more than welcome.
This is what I've tried so far
MongoClient mongoClient = new MongoClient();
DB sampleDB = mongoClient.getDB("sampleDB");
BasicDBObject update =
new BasicDBObject("$set", new BasicDBObject("name","john")
.append("$currentDate", new BasicDBObject("birthday",true)));
sampleDB.getCollection("col1").insert(update);
This thing fails on the following exception:
java.lang.IllegalArgumentException: Document field names can't start with '$' (Bad Key: '$set')
at com.mongodb.DBCollection.validateKey(DBCollection.java:1845)
at com.mongodb.DBCollection._checkKeys(DBCollection.java:1803)
at com.mongodb.DBCollection._checkObject(DBCollection.java:1790)
at com.mongodb.DBCollectionImpl.applyRulesForInsert(DBCollectionImpl.java:392)
at com.mongodb.DBCollectionImpl.insertWithCommandProtocol(DBCollectionImpl.java:381)
at com.mongodb.DBCollectionImpl.insert(DBCollectionImpl.java:186)
at com.mongodb.DBCollectionImpl.insert(DBCollectionImpl.java:165)
at com.mongodb.DBCollection.insert(DBCollection.java:93)
at com.mongodb.DBCollection.insert(DBCollection.java:78)
at com.mongodb.DBCollection.insert(DBCollection.java:120)
In which case the answer is fairly simple. It is really about serializing from java BasicDBObject classes to the basic MongoDB interpretation. Without respect to your actual "query" document the "update" document part of your statement should be:
BasicDBObject update = new BasicDBObject("$set", new BasicDBObject("name","john")
.append("$currentDate", new BasicDBObject("birthrhday",true))
;
Which will indeed use the "server time" at the point of "update insertion" or "modification" with respect to the $currentDate modifier as used.
Just to be clear here, you don't use the .insert() method but an "upsert"operation with .insert(). The "query" and "update" syntax applies. Also see the $setOnInsert operator for specifically not modifying existing documents.
You can also use aggregation variable "$$NOW" if you are using an aggregation pipeline with update method.
MongoClient mongoClient = new MongoClient();
DB sampleDB = mongoClient.getDB("sampleDB");
BasicDBObject update =
new BasicDBObject("$set", new BasicDBObject("name","john")
.append("birthday", new BsonString("$$NOW")));
sampleDB.getCollection("col1").updateOne(query, List.of(update));
You can also use "$$NOW" with aggregation operators such as $add, $subtract, and many more, to compute more specific values (including dates) on the database side.
If you want to pass the Application Server's time instead of Database time, use the following code to send the current time. You should decide whether to use this in case if the Application Server time differs from Database Server time.
new BsonDateTime(Instant.now().toEpochMilli())
Sample Code:
MongoClient mongoClient = new MongoClient();
DB sampleDB = mongoClient.getDB("sampleDB");
BasicDBObject update =
new BasicDBObject("$set", new BasicDBObject("name","john")
.append("birthday", new BsonDateTime(Instant.now().toEpochMilli())));
sampleDB.getCollection("col1").updateOne(query, update);
I'm relatively new to MongoDB and I'm currently working with java towards a "find by most tags matching" solution to information within a collection.
I'm stuck now trying to translate a MongoDB shell operation to the JAVA driver version (this sintaxis is part of the definitions needed )
$cond:[{$eq: ["$tags", 200]}, 1, 0]
What would be a correct JAVA implementation for the sentence above?
Thank you in advance
Whatever the $cond object where in your aggregation operation, to build it to should do something like this:
BasicDBList eqList = new BasicDBList();
eqList.add("$tags");
eqList.add(200);
DBObject eqObject = BasicDBObjectBuilder.start()
.add("$eq", eqList)
.get();
BasicDBList condList = new BasicDBList();
condList.add(eqObject);
condList.add(1);
condList.add(0);
DBObject condObject = BasicDBObjectBuilder.start()
.add("$cond", condList)
.get();
I'm confusing about your aggregation operation, could you give more details?