18

References:

Still pretty new to mongo db but I'm trying to update part of an existing document inside a collection... unfortunately, the link above doesn't have an update example.

Essentially, i just want to be able to:

  1. Add new fields to a document
  2. Update existing fields of a document to a new value

Here's my code (Grails + Groovy + Java + MongoDB + the java driver):

def shape = mongo.shapes.findOne(new BasicDBObject("data", "http://www.foo.com")); // get the document
mongo.shapes.update(new BasicDBObject("_id", shape._id), new BasicDBObject("isProcessed", 0));  // add a new "isProcessed" field set to 0
mongo.shapes.update(new BasicDBObject("_id", shape._id), new BasicDBObject("data", "http://www.bar.com"));

This pretty much clobbers the entire object... I might try just modifying the original shape object and then running the update on that. But until then, does anyone have experience updating just individual fields (rather than the entire document)?

EDIT:

I just tried it and was able to successfully update by sending the entire object across with new and/or updated fields and that works. I wonder if the driver is smart enough to only update the smallest subset of changes or if it's just blindly updating the entire thing? (In the case below, is it just updating the foo field across the wire or the entire shape document?)

Code:

def shape = mongo.shapes.findOne(); // get the first shape to use as a base
shape.removeField("_id");  // remove the id field
shape.put("foo","bar");  // add a new field "foo"
mongo.shapes.insert(shape);  // insert the new shape
def shape2 = mongo.shapes.findOne(new BasicDBObject("foo", "bar"));  // get the newly inserted shape (and more importantly, it's id)
shape2.put("foo", "bat");  // update the "foo" field to a new value
mongo.shapes.update(new BasicDBObject("_id", shape2._id), shape2);  // update the existing document in mongo
Parvin Gasimzade
  • 25,180
  • 8
  • 56
  • 83
longda
  • 10,153
  • 7
  • 46
  • 66

6 Answers6

12

I wonder if the driver is smart enough to only update the smallest subset of changes or if it's just blindly updating the entire thing?

No, if you use the "normal" update method, the whole object will be sent over the wire. I suspect that the database server itself will be clever enough to only update the necessary indexes (and not the ones that did not change), if possible (i.e. the object could be updated in place and did not have to be moved because it grew too much)

What you can do is use the "atomic update modifier" functions. The Java documentation is a bit light on them, but since the driver just transmits the JSON, the stuff from the non-Java tutorials should work, for example:

shapes.update((DBObject)JSON.parse(    "{ 'foo' : 'bar'}"),  
    (DBObject) JSON.parse(          "{ '$set' : { 'foo': 'bat'}}")   );
Thilo
  • 257,207
  • 101
  • 511
  • 656
  • 1
    Thanks! I think I like this code a bit better as it's slightly clearer than the standard syntax... I'll try it out. – longda Aug 27 '10 at 17:54
  • I tried this and could not get it to parse. It didn't like the single quotes, and then the second JSON.parse cannot be cast to a DBObject (it's a HashMap). – Gandalf Oct 19 '10 at 15:07
  • @Gandalf: Sorry about that, untested code. But it should be very similar to the above. – Thilo Oct 20 '10 at 01:17
11

Found an example here, which seems to show the usage for the update call. So for your example, I believe something like this should work?

// Find an object
def shape2 = mongo.shapes.findOne( new BasicDBObject( 'foo', 'bar' ) )
// And update the foo field from 'bar' to 'bat'
mongo.shapes.update( shape2, new BasicDBObject( '$set', new BasicDBObject( 'foo', 'bat' ) ) )

Edit

You might be able to use a category to construct your BasicDBObjects in a more groovy way...

Something like this might do it:

class BasicDBObjectMapBuilder {
  static String toDbObj( String s ) { s }
  static BasicDBObject toDbObj( Map m ) {
    m.inject( null ) { r, it -> new BasicDBObject( it.key, it.value.toDbObj() ) }
  }
}

use( BasicDBObjectMapBuilder ) {
  def shape2 = mongo.shapes.findOne( new BasicDBObject( 'foo', 'bar' ) )
  // And update the foo field from 'bar' to 'bat'
  mongo.shapes.update( shape2, [ '$set':[ 'foo', 'bat' ] ].toDbObj() )
}

I haven't tested this though...

Edit 2

Actually, BasicDBObject is a Map, so you should be able to do:

  mongo.shapes.update( shape2, [ '$set':[ 'foo', 'bat' ] ] as BasicDBObject )

without needing the builder

tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • MY EYES! THEY BURN! Thank you so much for this, that makes total sense now... the code that they're making us write for this is SO ugly though! :) – longda Aug 27 '10 at 17:52
  • hahaha...yeah... I've put a rough attempt a a category to build the DBObjects up from a map in as an edit...not tested it tho... – tim_yates Aug 27 '10 at 22:22
  • Yeah, Java makes working with JSON ugly. The Java driver should provide ways to pass in JSON strings (but that is also hindered by lack of multi-line strings and alternate quote characters in Java), and also some builder methods with meaningful names (so you do not have to type $set). – Thilo Aug 28 '10 at 01:47
7

A lot of the answers on this post are using older versions of the Mongo Java Driver. If you're using a newer version of the Java Driver (v3.0+) then the preferred method seems to be to use the Document object instead of the DBObject interface.

Here is an example:

MongoClient client = new MongoClient();
MongoCollection<Document> fooCollection = client.getDatabase("test").getCollection("foo");

Bson filter = Filters.eq("_id", "123d45678c467bb433c99f99");
Bson updates = Updates.set("isFoo", true);
fooCollection.findOneAndUpdate(filter, updates);
robjwilkins
  • 5,462
  • 5
  • 43
  • 59
3

This answer uses the mongo shell, but shows how you can go deep into a JSON object structure to modify a specific field without overwriting the rest.

Given the JSON object in a collection called 'my_collection':

{ 
  "_id" : ObjectId("50fdb2a73f7bc7a5acecc4f8"), 
  "data" : { 
    "list" : [ 0, 1, 2, 7, 4, 5 ], 
    "subobj" : { 
       "var_a":"valuea",
       "var_b":"valueb" 
     }
  }
}

To update 'var_b' , without overwriting anything else:

db.my_collection.update({"_id":"50fdb2a73f7bc7a5acecc4f8"}, { "$set":{"data.subobj.var_b":"new_value"}})

To update the 3rd element in the array 'list' with value '99', without overwriting anything else:

db.my_collection.update({"_id":"50fdb2a73f7bc7a5acecc4f8"}, { "$set":{"data.list.2":"99"} } )
1
DBCollection dbCollection = db.getCollection("mycollection");
BasicDBObject dbObject = new BasicDBObject();
dbObject.put("_id", "3"); 
// your update condition - or the query
DBObject newObject =  dbCollection.find(dbObject).toArray().get(0);
// I just take the first element. Can iterate through as per your requirement if multiple fields        exist
newObject.put("key","value");
//add field, either a new field or any existing field
dbCollection.findAndModify(dbObject, newObject);

Just use the above steps . You can change the details without affecting other items associated to the same key.

Arun A K
  • 2,205
  • 2
  • 27
  • 45
0

// update

WriteResult usr = (WriteResult)mongoOperation.upsert(new  Query(Criteria.where("name").is("till")),  

Update.update("password", "jk45"), "collection");

System.out.println("updatedUser : " + usr );
zangw
  • 43,869
  • 19
  • 177
  • 214
user2164052
  • 41
  • 1
  • 1