Groovy and Grails’ speed and simplicity are a perfect match to the flexibility and power of MongoDB. Dozens of plugins and libraries connect these two together, making it a breeze to get Grooving with MongoDB.
For the purpose of this post, let’s pretend we’re writing a hospital application that uses the following domain class.
class Doctor {
String first
String last
String degree
String specialty
}
There are a few grails plugins that help communicate with MongoDB, but one of the easiest to use is the one created by Graeme Rocher himself (Grails project lead). The MongoDB GORM plugin allows you to persist all your domain classes in MongoDB. To use it, first remove any unneeded persistance-related plugins after you’ve executed the ‘grails create-app’ command, and install the MongoDB GORM plugin.
Step 1: Remove the hibernate dependency from the BuildConfig.groovy file.
Step 2:Install the MongoDB GORM
plugin grails install-plugin mongodb
Step 3:Edit DataSource.groovy to specify connection settings. You can entirely remove the content of this configuration file and replace it with the following, or you can specify different connection settings for each of your environments (dev, test, prod):
grails {
mongo {
host = “localhost”
port = 27107
username = ”user”
password=”secretpassword”
databaseName = “physicians”
}
}
And that’s it! You can continue building your Grails application as usual by generating your controllers, views, services, etc.
Although GORM was specifically created to persist and manipulate objects in a ‘relational’ way, you can still use some of its powerful features with MongoDB. These include:
Doctor.list() Doctor.get(5) Doctor.getAll(5,6,7) Doctor.list(max:5,sort:'first',order:'desc') Doctor.listOrderBySpecialty()
Doctor.findByLast(“House”) Doctor.findAllBySpecialtyAndDegree(“Pediatrics”,”MD”)
Doctor.withCriteria{ eq("specialty", "Pediatrics") }
Doctor.withCriteria{ projections {countDistinct('specialty')} }
def sampledoc = new Doctor(specialty:"Pediatrics") def docs = Doctor.findAll(sampledoc)
Some of the GORM features that are not supported include:
This is where it gets exciting: with MongoDB, you can make domain objects do things that are impossible with a relational database. For example, you can now programmatically add properties on the fly based on your run time needs.
Let’s suppose that you want to add a ‘city’ property to your domain object. You can easily accomplish this without having to modify the original domain class using the subscript operator:
def doc = new Doctor(first:”Julius”,last:”Hibbert”, degree:”MD”,specialty:”General Medicine”) // Add a city dynamically to this doctor doc["city"]="Springfield" // Saving it creates an instance of Doctor with the new field doc.save()
Dynamic Finders even work with dynamically added fields:
def d = Doctor.findByCity(“Springfield”)
A word of caution about ID generation. Grails automatically assigns an ID as an integer value. When a Doctor object is saved, the plugin creates an _id within the document as follows:
{ "_id" : NumberLong(1),...}
However, a normal insert in MongoDB, creates it as a BSON ObjectId:
{ "_id" : ObjectId("4f2ca36b25f39d121957cebb"),...}
An extra collection is also created, doctor.next_id, to keep track of the next id.
To make ID generation compatible with MongoDB, you need to declare your ID explicitly as an ObjectID in your domain class:
import org.bson.types.ObjectId
class Doctor {
ObjectId id
…
}
You can use Groovy and its scripting and metaprogramming powers with the help of Gmongo.
Gmongo, written by Paulo Poiati, is a convenient wrapper around the MongoDB Java driver. Using Gmongo in your Groovy programs is the closest thing to writing commands directly into Mongo’s interactive shell.
In version 0.9.3, Gmongo can be instantiated by calling it through Grapes:
@Grab(group='com.gmongo', module='gmongo', version='0.9.3')
import com.gmongo.GMongo
// Instantiate gmongo.GMongo object
// It defaults to localhost, but you can pass connection settings through the constructor
def mongo = new GMongo()
// Get a reference to the db
def db = mongo.getDB("physicianfinder")
Now you have a database object, which you can attach to your collection to run MongoDB commands. Here’s an example of the syntax:
// [MongoDB database(usually just db)].[name of your collection].[command]
// The following example returns the number of documents in a collection
Integer howmanydoctors = db.doctor.count()
// Finding documents with Gmongo
// Returns a com.mongodb.DBCursor class
def doctors = db.physician.find()
// output each document to screen
doctors.each {
println it
}
// Finds and returns one doctor
db.doctor.findOne()
// Find all doctors with last name 'Wu'
db.doctor.find(last:"Wu")
// Return last and specialty fields only
db.hospitals.find([:],[last:1,specialty:1])
// Using a regex, find all docs whose last name starts with 'H'
db.doctor.find([last: ~/^H/])
// Find and sort by specialty
db.doctor.find().sort([specialty:1])
// Skip by 10, limit results by 5
db.doctor.find(specialty:”Pediatrics”).skip(10).limit(5)
Writing documents to MongoDB with Groovy is again very similar to how it’s done through the shell. You can also pass multiple types of collections to the insert method and have it magically saved correctly as a document.
// Inserting a Doctor with two fields
db.doctor.insert([first:"Charles",last:"Darwin"])
// Specifying write concern
db.doctor.insert([first:"Gregory",last:"House"], com.mongodb.WriteConcern.NORMAL)
// Inserting nested documents:
def hospital = [name:"My City Big Hospital", city:"Chicago",
buildings:[[address:"123 Main St.", name:"Feinberg",num_of_beds:300],
[address:"345 Hope Ln.", name:"Galter",num_of_beds:100]]]
// Saves the map of maps as a nested document
db.hospitals.insert(hospital)
// The following document is then created in the hospitals collection:
{ "_id" : ObjectId("4f2c98d53b7e58ee6ea885d5"),
"name" : "My City Big Hospital",
"city" : "Chicago",
"buildings" : [ { "address" : "123 Main St.", "name" : "Feinberg", "num_of_beds" : 300 },
{ "address" : "345 Hope Ln.", "name" : "Galter", "num_of_beds" : 100 } ] }
You can also use Groovy builders to generate your documents.
// Create a doctor with a JSON builder
def builder = new groovy.json.JsonBuilder()
builder.physicians {
doctor { first 'Julius' last 'Hibbert'
// Named arguments are valid values for objects too
address( city: 'Springield',
country: 'USA',
zip: 02105, )
medicalschool: "Johns Hopkins University School of Medicine"
mensamember true
practice 'Dr. Julius Hibbert, M.D. Family Practice' }
}
// Parse JSON string into a com.mongodb.util.JSON object
def parsed = com.mongodb.util.JSON.parse(builder.toString())
// Save to Mongo
db.physicians.insert(parsed)
Updating and removing are also straightforward:
// Look for 'Darwin' and update first name to 'Charles' db.doctor.update([last:"Darwin"],[$set:[first:"Charles"]]) // Delete all 'Darwins' db.doctor.remove(last:"Darwin")
If instead of collections you want to persist Plain Old Groovy Objects(POGOs), you can. Let’s start with our Doctor object from earlier:
class Doctor {
def first
def last
def specialty }
You can insert an instance of it by doing the following:
def doc = new Doctor(first:"Richard",last:"Kimble",specialty:"Pediatrics")
The doc instance also contains properties that we do not need to save into the document, so we remove the class and metaClass property before inserting:
// Get only the properties we want to save
def doctor = doc.properties.findAll { !['class', 'metaClass'].contains(it.key) }
// Insert it into the doctor collection
db.doctor.insert(doctor)
And we can retrieve it back as a POGO as well:
// Ignore the _id property
Doctor docFromMongo = db.doctor.findOne(last:"Kimble").findAll { it.key != '_id' }
And if you don’t have a POGO declared, you can use the magic of the Expando class to retrieve any document into a Groovy class:
Expando docFromMongo = db.doctor.findOne(last:"Kimble")
We’ve only covered the basics of interacting with MongoDB, but there are many more features you can leverage for making the most of the NoSQL world. To get started, check out the MongoDB GORM plugin.
— Ariel Gamino