By default MongoDB uses 12-byte BSON id for objects. For some reason I wanted to use an increasing sequence of integers.
The samples in documentation are in JSON and I was not sure how they translate to the Java driver. The JSON sample looks like:
function counter(name) { var ret = db.counters.findAndModify({query:{_id:name}, update:{$inc : {next:1}}, "new":true, upsert:true}); // ret == { "_id" : "users", "next" : 1 } return ret.next; } db.users.insert({_id:counter("users"), name:"Sarah C."}) // _id : 1 db.users.insert({_id:counter("users"), name:"Bob D."}) // _id : 2
After some googling I found an implementation in Java. Just as I expected, it’s much longer and completely different.
public static String getNextId(DB db, String seq_name) { String sequence_collection = "seq"; // the name of the sequence collection String sequence_field = "seq"; // the name of the field which holds the sequence DBCollection seq = db.getCollection(sequence_collection); // get the collection (this will create it if needed) // this object represents your "query", its analogous to a WHERE clause in SQL DBObject query = new BasicDBObject(); query.put("_id", seq_name); // where _id = the input sequence name // this object represents the "update" or the SET blah=blah in SQL DBObject change = new BasicDBObject(sequence_field, 1); DBObject update = new BasicDBObject("$inc", change); // the $inc here is a mongodb command for increment // Atomically updates the sequence field and returns the value for you DBObject res = seq.findAndModify(query, new BasicDBObject(), new BasicDBObject(), false, update, true, true); return res.get(sequence_field).toString(); }
Not much later I checked docs and source code for Congomongo and discovered fetch-and-modify
. I rewrote the Java sample above to Clojure and later polished it using code from this commit by Krzysztof Magiera. In the end my sequence generator looks like this:
(defn next-seq [coll] (with-mongo db (:seq (fetch-and-modify :sequences {:_id coll} {:$inc {:seq 1}} :return-new? true :upsert? true)))) (with-mongo db (insert! :books {:author "Adam Mickiewicz" :title "Dziady" :_id (next-seq :books)}))
The raw call to insert!
could be wrapped in a function or macro to save some boilerplate if there are more collections. For instance:
(defn insert-with-id [coll el] (insert! coll (assoc el :_id (next-seq coll)))) (with-mongo db (insert-with-id :books {:author "Adam Mickiewicz" :title "Dziady"}))
In some circles this probably is common knowledge, but it took me a while to figure it all out.