Implementing Burt Beckwith’s GORM Performance – No Collections

Posted on February 7, 2011. Filed under: grails |

No More GORM/Hibernate Collections?

Mr. Burt Beckwith http://burtbeckwith.com/ gave a talk on improving GORM performance in Grails. His presentation is available at http://www.infoq.com/presentations/GORM-Performance.

A point of his was to not use collections on your domain objects because the collections may need to be fully loaded from the database before objects can be added.

I decided to use it on one of my work projects. This project has 18 domain classes. I ran into a few problems, scratched my head for a few hours, then I was able to fix them. It took about 1/2 day to update the project from a “hasMany/belongsTo” style to a no collection style. Here is a very-simplified domain that uses a typical gorm one-to-many cascading relationship.

Sample Shopping Cart Application

Lets start with sample code of a Shopping Cart. We have a Cart object that contains many Item objects.

Typical GORM Relationship

grails-app\domain\shopping\Cart.groovy

package shopping

class Cart {

	String name

	static hasMany = [ items : Item ]

	static mapping = {
		sort "name"
		items sort:"name"
	}

    static constraints = {
    	name nullable:false, blank:false, unique:true
    }

    String toString() {
    	"Cart $name"
    }
}

grails-app\domain\shopping\Item.groovy

package shopping

class Item {

	String name
	Integer quantity

	static belongsTo = [ cart : Cart ]

	static mapping = {
		sort "name"
	}

    static constraints = {
    	name nullable:false, blank:false, unique:['cart']
    	quantity nullable:false
    }

    String toString() {
    	"$quantity $name"
    }
}

grails-app\conf\BootStrap.groovy

import shopping.*

class BootStrap {

    def init = { servletContext ->
    
    	def cart = new Cart(name:"alpha").save()
    	cart.addToItems new Item(name:"apples", quantity:10)
    	cart.addToItems new Item(name:"milk", quantity:2)
    	cart.addToItems new Item(name:"bread", quantity:3)
    	cart.save()
    }

    def destroy = {
    }
}

Removing hasMany and belongsTo

Now, following Burt’s advice, we remove the belongsTo and hasMany relationship, and embed a the parent object in the child.

Now our code looks like this:

BelongsTo and HasMany Removed

grails-app\domain\shopping\Cart.groovy

package shopping

class Cart {
	
	String name
	
	static mapping = {
		sort "name"
	}
	
    static constraints = {
    	name nullable:false, blank:false, unique:true
    }
    
    String toString() {
    	"Cart $name"
    }
}

grails-app\domain\shopping\Item.groovy

package shopping

class Item {
	
	String name
	Integer quantity
	Cart cart
	
	static mapping = {
		sort "name"
	}
	
    static constraints = {
    	name nullable:false, blank:false, unique:['cart']
    	quantity nullable:false
    }
    
    String toString() {
    	"$quantity $name"
    }
}

grails-app\conf\BootStrap.groovy

import shopping.*

class BootStrap {

    def init = { servletContext ->
    	def cart = new Cart(name:"alpha").save()
    	new Item(cart:cart, name:"apples", quantity:10).save()
    	new Item(cart:cart, name:"milk", quantity:2).save()
    	new Item(cart:cart, name:"bread", quantity:3).save()
    }
    
    def destroy = {
    }
}

Here’s what changed.

in Cart.groovy

  • removed static hasMany = [ items : Item ]
  • removed items sort:”name” from mappings

in Item.groovy

  • removed belongsTo
  • added Cart cart

in BootStrap.groovy

  • Changed the way we created child objects.
    • from: cart.addToItems new Item(name:”apples”, quantity:10)
    • to : new Item(cart:cart, name:”apples”, quantity:10)

New Problems and Solutions

Are we done? Of course not – it’s not quite that easy. There were a few other problems that needed to be addressed:

  1. No longer have easy access to the collection of child items.
  2. Sorting of collections (defined in mapping) no longer takes place.
  3. Can’t delete the parent object because of foreign-key relationships.
  4. Scaffolding no longer shows the child objects.

Lets solve these, one-at-a-time.

No longer have easy access to the collection of child items

This is easily fixed. We just add a new method to the parent object (Cart) that returns the collection of child objects (Item) that are in the cart:

grails-app\domain\shopping\Cart.groovy

    ....
    def getItems() {
    	Items.findAllByCart(this)
    }
    ....

Sorting of collections (defined in mapping) no longer takes place.

This is also easy. We just need a slight modification to the code above. We can add a sort parameter to the findAllBy* method.

grails-app\domain\shopping\Cart.groovy

    ....
    def getItems() {
        Item.findAllByCart(this, [sort:"name"])
    }
    ....

Now, we can access the cart’s items just like we did when we had a hasMany relationship.

    println cart.items

Can’t delete the parent object because of foreign-key relationships.

This was the head-scratcher. Not because it’s difficult, but it took a little research to find a elegant solution.

The problem is if we do a cart.delete(), it will fail it there are items in the cart. The old hasMany / belongsTo solution would automatically cascade-delete the items, and the cart. We don’t have that now, so we must do it manually.

The best solution that I know of (let me know if there is a better one) is to use the beforeDelete event on the Cart object.

Before a object is deleted, GORM fires the beforeDelete event. You can place code in it to do whatever you want. In our case, we will delete the child Item objects. Take a look at the documentation for beforeDelete .

Lets update the Cart.groovy code and add the beforeDelete method.

grails-app\domain\shopping\Cart.groovy

    ....
    def beforeDelete() {
        Item.withNewSession { items*.delete() }	
    }
    ....

(This is what I love about groovy – one line of code does so much work!)

First, we create a new hibernate session. If you read the docs for beforeDelete, you know that (in this case) you should use a new session for the delete in order to avoid StackOverflow exceptions.

Next, we delete all of the child items. We use groovy’s spread operation to call a method on all items in a list.

So, with that line of code, all child objects (Item) will be deleted when the parent object (Cart) is deleted.

Scaffolding no longer shows the child objects.

Since the parent object no longer knows about the child objects, the default grails scaffolding will now longer display the items in a cart. I don’t know a way around this, other than not using scaffolding.

Final Cart and Item Code

grails-app\domain\shopping\Cart.groovy

package shopping

class Cart {
	
	String name

	static mapping = {
		sort "name"
	}

	static constraints = {
		name nullable:false, blank:false, unique:true
	}

	String toString() {
		"Cart $name"
	}

	def getItems() {
		Item.findAllByCart(this, [sort:"name"])
	}

	def beforeDelete() {
		Item.withNewSession { items*.delete() }	
	}
    
}

grails-app\domain\shopping\Item.groovy

package shopping

class Item {

	String name
	Integer quantity
	Cart cart

	static mapping = {
		sort "name"
	}

	static constraints = {
		name nullable:false, blank:false, unique:['cart']
		quantity nullable:false
	}

	String toString() {
		"$quantity $name"
	}
}

My Opinions

Implementation Difficulty

Once I worked out how to implement this solution, installing it in my domain objects was pretty simple. Remember that you may need to update any code that uses addTo* and removeFrom* methods

Complexity / Maintenance

Does this solution make my apps more complex to understand? I don’t think so. A experienced groovy person can understand the cart.getItems() method. The beforeDelete will take a little time to understand, but it is quickly learned.

Also there is complexity in using belongsTo / hasMany. See GORM Gotchas Part 2. I don’t think this solution is any more complex the using belongsTo / hasMany.

Performance

Don’t know yet. I don’t have any real-world apps that I considered the loading of child objects to be a performance problem.

Would I Do It Again

Definitely. I had a project fall-apart because it has > 20 objects and >30 relationships between objects. Hibernate and GORM were causing various exceptions, and at the time I was too inexperienced to know how to solve the problems. I am considering attempting that project again, using this “no collections” technique to see if I can finish the project. I have a good feeling that this method will work, but I need to prove it.


Read Full Post | Make a Comment ( 23 so far )

Recently on Mr Paul Woods's Weblog…

A Pattern to Simplify Grails Controllers

Posted on January 23, 2011. Filed under: grails |

Dallas TechFest 2008

Posted on May 5, 2008. Filed under: training | Tags: |

Liked it here?
Why not try sites on the blogroll...

Follow

Get every new post delivered to your Inbox.