The way GORM for Mongo works is to map each domain class to a Mongo collection. For example given a domain class such as:class Person {
String firstName
String lastName
static hasMany = [pets:Pet]
}
This will map onto a Mongo DBCollection called "person".It is quite common in Mongo to embed documents within documents (nested documents). This can be done with GORM embedded types:class Person {
String firstName
String lastName
Address address
static embedded = ['address']
}
You can also map lists and maps of basic types (such as strings) simply by defining the appropriate collection type:class Person {
List<String> friends
Map pets
}...new Person(friends:['Fred', 'Bob'], pets:[chuck:"Dog", eddie:'Parrot']).save(flush:true)
Basic collection types are stored as native ArrayList and BSON documents within the Mongo documents.You may wish to customize how a domain class maps onto a DBCollection
. This is possible using the mapping
block as follows:class Person {
..
static mapping = {
collection "mycollection"
database "mydb"
}
}
In this example we see that the Person
entity has been mapped to a collection called "mycollection" in a database called "mydb".You can also control how an individual property maps onto a Mongo Document field (the default is to use the property name itself):class Person {
..
static mapping = {
firstName field:"first_name"
}
}
By default in GORM entities are supplied with an integer-based identifier. So for example the following entity:Has a property called id
of type java.lang.Long
. In this case GORM for Mongo will generate a sequence based identifier using the technique described in the Mongo documentation on Atomic operations.However, sequence based integer identifiers are not ideal for environments that require sharding (one of the nicer features of Mongo). Hence it is generally advised to use either String based ids:class Person {
String id
}
Or a native BSON ObjectId:import org.bson.types.ObjectIdclass Person {
ObjectId id
}
BSON ObjectId
instances are generated in a similar fashion to UUIDs
.Mongo doesn't require that you specify indices to query, but like a relational database without specifying indices your queries will be significantly slower.With that in mind it is important to specify the properties you plan to query using the mapping block:class Person {
String name
static mapping = {
name index:true
}
}
With the above mapping a Mongo index will be automatically created for you. You can customize the index options using the indexAttributes
configuration parameter:class Person {
String name
static mapping = {
name index:true, indexAttributes: [unique:true, dropDups:true]
}
}
A feature of Mongo is its ability to customize how important a database write is to the user. The Java client models this as a WriteConcern and there are various options that indicate whether the client cares about server or network errors, or whether the data has been successfully written or not.If you wish to customize the WriteConcern
for a domain class you can do so in the mapping block:import com.mongodb.WriteConcernclass Person {
String name
static mapping = {
writeConcern WriteConcern.FSYNC_SAFE
}
}
Unlike a relational database, Mongo allows for "schemaless" persistence where there are no limits to the number of attributes a particular document can have. A GORM domain class on the other hand has a schema in that there are a fixed number of properties. For example consider the following domain class:class Plant {
boolean goesInPatch
String name
}
Here there are two fixed properties, name
and goesInPatch
, that will be persisted into the Mongo document. Using GORM for Mongo you can however use dynamic properties via the Groovy subscript operator. For example:def p = new Plant(name:"Pineapple")
p['color'] = 'Yellow'
p['hasLeaves'] = true
p.save()p = Plant.findByName("Pineapple")println p['color']
println p['hasLeaves']
Using the subscript operator you can add additional attributes to the underlying DBObject
instance that gets persisted to the Mongo allowing for more dynamic domain models.It is possible to use Mongo's Geospacial querying capability by mapping a list or map property using the geoIndex
mapping:class Hotel {
String name
List location static mapping = {
location geoIndex:true
}
}
By default the index creation assumes latitude/longitude and thus is configured for a -180..180 range. If you are indexing something else you can customise this with indexAttributes
class Hotel {
String name
List location static mapping = {
location geoIndex:true, indexAttributes:[min:-500, max:500]
}
}
You can then save Geo locations using a two dimensional list:new Hotel(name:"Hilton", location:[50, 50]).save()
Alternatively you can use a map with keys representing latitude and longitude:new Hotel(name:"Hilton", location:[lat: 40.739037, long: 73.992964]).save()
Once you have your data indexed you can use Mongo specific dynamic finders to find hotels near a given a location:def h = Hotel.findByLocationNear([50, 60])
assert h.name == 'Hilton'
You can also find a location within a box (bound queries). Boxes are defined by specifying the lower-left and upper-right corners:def box = [[40.73083, -73.99756], [40.741404, -73.988135]]
def h = Hotel.findByLocationWithinBox(box)
You can also find a location within a circle. Circles are specified using a center and radius:def center = [50, 50]
def radius = 10
def h = Hotel.findByLocationWithinCircle([center, radius])
If you plan on querying a location and some other value it is recommended to use a compound index:class Hotel {
String name
List location
int stars static mapping = {
compoundIndex location:"2d", stars:1
}
}
In the example above you an index is created for both the location and the number of stars a Hotel
has.