GORM for MongoDB: New Milestone, Richer Experience |
|

Last year we introduced support for MongoDB in GORM (along with many other GORM implementations) and it has been extremely well received by the community. We have had a ton of feedback, and today we are pleased to announce a new release (Milestone 2) which addresses some of the feedback we have received.
Embedded Document Support
The number one requested feature was nested document support and in this release we have improved that significantly. Now you can embed other domains using the standard embedded mapping in GORM:
class Person {
String firstName
String lastName
Address address
static embedded = ['address']
}
The embedded domains get stored in a nested document within the primary Mongo document. In addition, lists and maps of basic types can now also be persisted to native Mongo ArrayList and hashes:
class Person {
List<String> friends
Map pets
}
...
new Person(friends:['Fred', 'Bob'], pets:[chuck:"Dog", eddie:'Parrot']).save(flush:true)
Both embedded domains and lists and maps get stored within the primary Mongo document for the domain allowing more of the common MongoDB patterns to be implemented using GORM.
Geospacial Indexing and Querying
MongoDB has native support for Geospacial indexing and querying and this is now supported in GORM for MongoDB. You can define a list or map as being "geo-indexed":
class Hotel {
String name
List location
static mapping = {
location geoIndex:true
}
}
And then easily persist the geo data using a two-dimensial list representing latitude and longitude:
new Hotel(name:"Hilton", location:[50, 50]).save()
Alternatively, and possibly more readable, you can use a map containing the latitude and longitude values:
new Hotel(name:"Hilton", location:[lat: 40.739037, long: 73.992964]).save()
Once persisted a domain class can then be queried with the new findBy*Near syntax:
def h = Hotel.findByLocationNear([50, 60]) assert h.name == 'Hilton'
You can also use bound queries to locate a position within a rectangle using the findBy*WithinBox method :
def box = [[40.73083, -73.99756], [40.741404, -73.988135]] def h = Hotel.findByLocationWithinBox(box)
Or within a circle using the findBy*WithinCircle method:
def center = [50, 50] def radius = 10 def h = Hotel.findByLocationWithinCircle([center, radius])
Schemaless Domain Models
MongoDB is completely schemaless meaning you are not limited to a fixed number of columns like in a relational database. GORM for MongoDB now supports schemaless domain models. You can continue to specify your fixed domain properties inside your domain model:
class Plant {
boolean goesInPatch
String name
}
However, you can now also persist additional properties using the Groovy subscript operator:
def p = new Plant(name:"Pineapple")
p['color'] = 'Yellow'
p['hasLeaves'] = true
p.save()
p = Plant.findByName("Pineapple")
println p['color']
println p['hasLeaves']
There are many more improvements including the ability to customize index creation, support for query-by-example and more complete support for the GORM API. The documentation has been updated to cover all these new features. Let us know what you think, your feedback is invaluable.
One final plug, if you are in the Madrid area and want to know more about GORM for MongoDB come down to the Spring IO conference this Thursday where there will be many more talks on Spring, Grails and GORM.
Similar Posts
- Yet Another Flavour of GORM: MongoDB
- What's New in Spring Integration 2.2.RC1 (Part 1 – MongoDb)
- Announcing GORM for Redis
- Deeper into Grails & Cloud Foundry
- Introducing GORM for Gemfire





Laurent Gaches says:
Added on February 15th, 2011 at 6:43 amBroke a lot of things. Transaction don't work anymore. read only properties too.
grocher (blog author) says:
Added on February 15th, 2011 at 7:04 am@Laurent Maybe if you can provide more details in a JIRA ( http://jira.codehaus.org/browse/GRAILSPLUGINS ) that would help. We have tests covering transaction support, it may be something specific to your application. I'm not sure what you mean by read-only properties, an example would help.
Chanwit Kaewkasi says:
Added on February 15th, 2011 at 7:19 amI am a couple of days late to file this issue. Two others (Redis and Riak) have been changed their been names to 'springDatastore'. I think it would be great to have the same bean name as those two.
grocher (blog author) says:
Added on February 15th, 2011 at 7:52 am@Chanwit We changed the bean names to avoid conflicts in the case where you have both the mongodb and redis plugins installed. If all plugins used 'springDatastore' there would be conflicts.
Chanwit Kaewkasi says:
Added on February 15th, 2011 at 10:20 amThanks, Graeme. This seems to be a design decision I overlooked.
Xristofer says:
Added on February 16th, 2011 at 7:14 pmI am still having issues with loading a document from the database that has an association, editing it, and saving it back. It fails trying to persist a BSONObject. If I manually delete the associated document from Mongo, it proceeds. I assume it's doing eager fetching and the associated document is left as its raw type when brought in by the driver?
Dynamic finders are also not working with properties mapped with 'attribute'. I know this one is undocumented but I thought I would throw it out there.
Great work on the improvements though! Embedded documents and dynamic properties are nice additions.
grocher (blog author) says:
Added on February 17th, 2011 at 3:34 am@Xristofer Can you report issues and attach examples of the problems you are seeing: JIRA ( http://jira.codehaus.org/browse/GRAILSPLUGINS )
Thanks!
Dmitry says:
Added on February 17th, 2011 at 9:00 amThanks for the update!
Unfortunately, it breaks down the code.
1) Read-only properties like spring-security-core User code lead to compile errors:
Set getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
I've added the setter to allow compilation go ahead:
void setAuthorities(Set authorities){
authorities.each {
UserRole.create(this, it)
}
}
2) For some reason
assert Domain.("hasMany" property).contains("object it really do contain")
always leads to a failure. Changed to .any{it.id==obj.id}.
3) Transactions doesn't work.
Exception Message: Already value [org.springframework.datastore.mapping.transactions.SessionHolder@ba117b] for key [org.springframework.datastore.mapping.mongo.MongoDatastore@380f83] bound to thread [http-8080-3]
Caused by: Could not open Datastore Session for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.datastore.mapping.transactions.SessionHolder@ba117b] for key [org.springframework.datastore.mapping.mongo.MongoDatastore@380f83] bound to thread [http-8080-3]
So I've added
static transactional = false
to all my services. Also I have commented all "Domain.withTransaction" statements.
4) Even after that I'm able to run tests and run project in development environment, but cannot deploy. Using Tomcat 7, Grails 1.3.6.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageSource': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoTransactionManager': Cannot resolve reference to bean 'mongoDatastore' while setting bean property 'datastore'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoDatastore': FactoryBean threw exception on object creation; nested exception is java.lang.NoSuchMethodError: org.springframework.data.document.mongodb.MongoTemplate.execute(Lorg/springframework/data/document/mongodb/DbCallback;)Ljava/lang/Object;
Thou I'd better revert to M1 version. Or am I just doing something wrong?
grocher (blog author) says:
Added on February 17th, 2011 at 9:09 am@Dmitry Can you raise JIRA issues with examples attached for the issues you are seeing? That feedback would be much appreciated. A simple test of withTransaction seems to work for me.
The last error you have seems to be due to conflicting versions of spring-data-mongo. Can you check of your WEB-INF/lib directory and make sure you only have M1 of the spring-data-mongo? Thanks.
Laurent says:
Added on February 17th, 2011 at 9:30 am@Dimitry you can find how to use Spring security core and mongodb here : https://github.com/lgaches/grails-mongospringsecurity
Dmitry says:
Added on February 17th, 2011 at 10:01 am@grocher just have it done.
I have two spring-data-mongodb jars in WEB-INF/lib even if I only create a new blank application without Hibernate and with Mongo, can't figure it out.
@Laurent it looks like this code won't work: try to cover UserRole with tests fully. HQL doesn't work with MongoDB.
Ryan Vanderwerf says:
Added on February 18th, 2011 at 3:16 pmMany thanks, now I don't have to mess with that dynamic properties plugin! I think this really unleashes the power of a schema-less database when you can persist dynamic stuff.
Matthias says:
Added on February 19th, 2011 at 7:47 pmI guess a similar problem as with the M1 occurs – the class MongoOptionsFactoryBean could not be found:
Error executing script Console: No such property: MongoOptionsFactoryBean for class: MongodbGrailsPlugin
No such property: MongoOptionsFactoryBean for class: MongodbGrailsPlugin
Any idea on how to fix this?
Xristofer says:
Added on February 19th, 2011 at 9:02 pm@grocher, my issues appear to be IDE related. I switched to IntelliJ from STS as of my last post and the issue goes away even using the same project directory.
Octavian Covalschi says:
Added on February 21st, 2011 at 12:06 amThanks again!
I'm on M4 now, so far works pretty well, although I've just started the project…
Anyway, couple of comments for this version, which I consider would be a nice to have:
- Embedded document of ObjectId type. So far it works only with embedded docs that are existing domains (created with create-domain-class), however it doesn't work if I have an ObjectId property and trying to save it, I'm getting a NPE. This is useful if someone decided to use ObjectId as domain's class id and want to save it as a reference within another domain class. For now String id is enough, but allowing ObjectId id is pretty useless.
- Atomic operations. Not sure if or how they'll fit into Gorm, but I think it would be nice to have at least some of them implemented. For example: instead of 'User.collection.update([_id: session.auth.id], [$inc: [invitesLeft: -1]])' to have something like 'User.incInvitesLeft(id)' or similar.
Again, thanks a lot for richer experience.
Dmitry says:
Added on February 21st, 2011 at 6:12 amM4 really allows deployment without compile fails. But it's still not production-ready.
- Sometimes Mongo objects are returned instead of domains (in production mode). In my case, it is simple "hasMany" association. In development it's okay.
- Transactions doesn't work. When in UserDetailsService I type
User.withTransaction { status ->
It fails with session error:
org.springframework.transaction.CannotCreateTransactionException: Could not open Datastore Session for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.datastore.mapping.transactions.SessionHolder@2eb89c06] for key [org.springframework.datastore.mapping.mongo.MongoDatastore@7248989f] bound to thread [http-8080-3]
at org.springframework.datastore.mapping.transactions.DatastoreTransactionManager.doBegin(DatastoreTransactionManager.java:148)
Caused by: java.lang.IllegalStateException: Already value [org.springframework.datastore.mapping.transactions.SessionHolder@2eb89c06] for key [org.springframework.datastore.mapping.mongo.MongoDatastore@7248989f] bound to thread [http-8080-3]
at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:179)
at org.springframework.datastore.mapping.transactions.DatastoreTransactionManager.doBegin(DatastoreTransactionManager.java:129)
… 79 more
Hope production-ready plugin will be available soon. All of us need it so much.
And one question. Is there any method to get all possible keys of a domain object? For [...] syntax.
Thanks for a fast bug-fixing!
Dmitry says:
Added on February 21st, 2011 at 6:38 am@grocher I'd be glad to to help you developing the plugin in any way if it is possible
grocher (blog author) says:
Added on February 21st, 2011 at 6:49 am@Dmitry Contributions are welcome. Feel free to fork the repository at https://github.com/grails/inconsequential
What would be good otherwise is some reproducible examples attached to the JIRAs you raised. That would really help in fixing the problems you have found so far.
Octavian Covalschi says:
Added on February 21st, 2011 at 11:15 pmI'm having some issues with gorm's delete. It doesn't work!
Test below always fails… the record/document gets created, but .delete() doesn't work!!! I've tried within a controller as well, no luck…
Can anyone confirm this issue? Or I'm missing something?
Thanks.
PS: This integration test always fails..
void test_page() {
def p = new Page(title: "About", body: "body", isPublic:true).save(flush: true)
def p2 = Page.get(p.id)
assertNotNull p2
p2.delete(flush:true)
def p3 = Page.get(p.id)
assertNotNull p3
}
Octavian Covalschi says:
Added on February 23rd, 2011 at 7:51 amI have created a JIRA issue with delete http://jira.codehaus.org/browse/GRAILSPLUGINS-2886
Hope that helps.
Matthias Hryniszak says:
Added on February 24th, 2011 at 6:14 pm@grocher With M4 the dependency problem seems to be gone. Thanks!
jjchiw says:
Added on February 24th, 2011 at 8:49 pmI'm not really sure, does the unique constraint should work?
Should I create a JIRA issue….
Thanks for the plugin
Octavian Covalschi says:
Added on February 24th, 2011 at 9:45 pm@jjchiw
Yes, it does, check indexAttributes
This sample is from plugin's docs:
class Person {
String name
static mapping = {
name index:true, indexAttributes: [unique:true, dropDups:true]
}
}
jjchiw says:
Added on February 26th, 2011 at 11:22 am@Octavian
Hi, thanks for the information
Even it works the mapping, the constraints in the Domain Model, it seems it does not works…
When I do
import org.bson.types.ObjectId
class Foo {
ObjectId id
String name
static constraints = {
name nullable:false, blank:false, matches:"[\\d] ", unique:true
}
static mapping = {
name index:true, indexAttributes:[unique:true, dropDups:true]
}
}
def f = new Foo(name:"456")
f.validate() //it's always true
So, should I create a JIRA issue?
imran says:
Added on March 3rd, 2011 at 12:53 amI've seen the problem with constraints as well. nullable fields are not allowed to be to set to null.
imran says:
Added on March 3rd, 2011 at 10:55 amFrom the mongodb website regarding null values which I am currently having problems with :
Are null values allowed?
For members of an object, yes. You cannot add null to a database collection though as null isn't an object. You can add {}, though.
Perhaps the GORM api needs to be updated to set the value to {}?
German says:
Added on March 17th, 2011 at 11:16 amWould it be feasible/possible/convenient to have a "Gorm for db4o" ? http://developer.db4o.com
Graeme Rocher says:
Added on April 4th, 2011 at 3:02 am@German Yes GORM for db4o is possible based on the existing infrastructure we have. Contributions welcome
jzhwu says:
Added on April 6th, 2011 at 3:04 amI have trouble finding the version 1.0.0.M2.
There are 4 different versions in svn.codehaus.org, including:
# RELEASE_1_0-M2/
# RELEASE_1_0-M3/
# RELEASE_1_0-M4/
# RELEASE_1_0_0_M1/
I'm not sure about which one is the version announced in this message.
imran says:
Added on April 8th, 2011 at 8:32 amwhen is version 5 going to be available?
Graeme Rocher says:
Added on April 8th, 2011 at 9:23 am@Imran Next week sometime
@Jzhwu Use the M4 version for the moment until M5 is out
jzhwu says:
Added on April 9th, 2011 at 4:10 amThanks! I've tried the new features, which is exactly what I was looking for. Well done!
Sam says:
Added on April 18th, 2011 at 2:40 amI'm new to Mongo GORM and many thanks to the posts here which answered the problems I have encountered, I'm using mongodb-1.0-M5 btw which still appears to have many of these problems.
One thing I noticed is that domain classes are based on application name prefix in a package like structure, I see in the logs for example:
Mon Apr 18 00:13:53 [conn11] building new index on { _id: 1 } for testwebapp.requestMap
I was expecting the package name of the class to be be used so com.myapp.RequestMap for example
Is this expected behavior?
Thanks
jzhwu says:
Added on April 21st, 2011 at 3:46 amAnother question:
In your article's dynamic property demonstration, the object must be initialized with properties defined in Domain class to enable further dynamic properties:
1 def p = new Plant(name:"Pineapple")
2 p['color'] = 'Yellow'
3 p['hasLeaves'] = true
I tried to directly add dynamic properties to an object constructed without any parameter, like:
1 def p = new Plant()
2 p['color'] = 'Yellow'
3 p['hasLeaves'] = true
Executing above code failed, resulting in an exception:
java.lang.IllegalStateException: Cannot obtain DBObject for transient instance, save a valid instance first
at org.grails.datastore.gorm.mongo.MongoGormInstanceApi.getDbo(MongoGormEnhancer.groovy:120)
at org.grails.datastore.gorm.mongo.MongoGormInstanceApi$getDbo.callCurrent(Unknown Source)
at org.grails.datastore.gorm.mongo.MongoGormInstanceApi.putAt(MongoGormEnhancer.groovy:81)
at org.grails.datastore.gorm.GormEnhancer$1.call(GormEnhancer.groovy:93)
The fundamental problem is that I need to supply all the dynamic properties, which have been configured in another Domain class, together with pre-defined ones to create.gsp, so that user can input all the data items. Now it seems that dynamic properties can only be injected after pre-defined ones' values being set. Any solutions? Many thanks!
Tigran says:
Added on May 16th, 2011 at 8:58 amHi guys,
I have not experience in Grails (1.3.6).
Is it possible to save in db (Mongo) domain with embedded domain which contains domain as well?
Thanks in advance.
Simon says:
Added on May 18th, 2011 at 9:52 pmcan save the List(no basic types) as embed documents?
like:
class User{
String firstName
List authorities
static embedded = ['authorities']
}
class Role{
String authority
}
numan says:
Added on May 22nd, 2011 at 6:32 pmdoes this and the redis plugin use spring data project under the covers?
curious says:
Added on May 23rd, 2011 at 9:40 amI´m trying to use the GORM´s example to embedded documents.
class Person {
static mapWith = "mongo"
Address homeAddress
Address workAddress
static embedded = ['homeAddress', 'workAddress']
}
class Address {
static mapWith = "mongo"
String number
String code
}
However, I get an error like : Cannot resolve reference to bean 'mongoMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoMappingContext': FactoryBean threw exception on object creation; nested exception is org.springframework.datastore.mapping.model.IllegalMappingException: Mapped identifier [id] for class [Address] is not a valid property
I have the M6 version of mongoDB plugin for grails…
Graeme Rocher says:
Added on May 23rd, 2011 at 9:45 am@Simon Yes you can ask of M6 release
@numan
You may have run into http://jira.grails.org/browse/GPMONGODB-21
See the workaround in that issue.
If the problem persists please report an issue at http://jira.grails.org/browse/GPMONGODB
Vikram G says:
Added on August 19th, 2011 at 4:43 pmWhen I try to assign my own id instead of generated id, the save fails with the following exception. Am I missing something here.
I tried save(insert:true).. no joy.
class Product {
String id;
String description;
String internalName;
String brandName;
static mapping = {
id generator:'assigned'
}
}
java.lang.NullPointerException
at org.grails.datastore.mapping.mongo.engine.MongoEntityPersister.isDirty(MongoEntityPersister.java:541)
at org.grails.datastore.mapping.core.AbstractSession.isDirty(AbstractSession.java:257)
at org.grails.datastore.mapping.engine.NativeEntryEntityPersister.persistEntity(NativeEntryEntityPersister.java:603)
Harsh says:
Added on October 27th, 2011 at 7:05 pmI have the same exact issue as Vikram G. Anyone else?
David Castro says:
Added on October 30th, 2011 at 6:15 amjzhwu, I had the same problem that you did. If you initialize the object with a throwaway value from the domain class (I chose "name" and set it to a value that will never be referenced), then the object will work. This seems like a bug, but the workaround is so simple that I'm unlikely to log it.
Roger says:
Added on December 21st, 2011 at 9:18 amIt seems, with 1.0.0.RC3, that embedded COLLECTIONS of objects work 0% of the time.
I can do:
SomeObject so
static embedded = ['so']
just fine.. but if I do:
List so
static embedded = ['so']
a) the collection is null, so I have to manually instantiate it (not true with say, hasMany.. or not consistent).
b) when I save the object, the embedded COLLECTION does not get saved (literally that entire 'key' is non-existant in mongo)