Grails in the Land of MongoDB

Feb 29 • Posted 2 years ago

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.

Using Grails 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.

Using MongoDB GORM for Persistence

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.

GORM and Mongo

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:

Basic GORM:
Doctor.list() 
Doctor.get(5) 
Doctor.getAll(5,6,7) 
Doctor.list(max:5,sort:'first',order:'desc')
Doctor.listOrderBySpecialty() 
Dynamic Finders:
Doctor.findByLast(“House”) 
Doctor.findAllBySpecialtyAndDegree(“Pediatrics”,”MD”)
Criteria Queries:
 Doctor.withCriteria{ eq("specialty", "Pediatrics") }
Projections:
Doctor.withCriteria{ projections {countDistinct('specialty')} }
Query by Example:
def sampledoc = new Doctor(specialty:"Pediatrics") 
def docs = Doctor.findAll(sampledoc)

Some of the GORM features that are not supported include:

  • Criteria queries on associations
  • HQL
  • Groovy SQL

The Schemaless Universe in a GORM World

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.

Dynamic Attributes

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”)

Grails Identifiers and MongoDB Identifiers

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
     …
}

Groovy and MongoDB

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) 

Saving data to MongoDB

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

blog comments powered by Disqus
blog comments powered by Disqus