Beware: Controller and Service Dirt and Flush

2009 April 12
by noisyheads

From my earlier post, I’ve mentioned that I am now keeping a cleaner approach as regards GORM methods in either a controller or a service. Just when I thought I got that all figured out, (hibernate) transaction pandemonium happened to me.

Fact, each GORM call opens, if not already is, a hibernate session. So a “get” would actually give you a hibernate object that currently belongs to the persistent context. A “dirty” domain object is the result of modifying it’s properties, unless of course it is explicitly a “read-only” object.  Changes that made the object “dirty”  wouldn’t otherwise be persisted if a discard is invoked on it. The thing that might surprise most people though is that once the session is flushed somehow, for example a save(flush:true) is invoked on another domain object somewhere along the method, the “dirty” object gets saved as well, even without invoking a save() on it. This bypasses all the validation thingos that comes along with GORM.

Given that, I encountered a problem that might be triggering this session flush unexpectedly. This is when the controller action invokes a service that is transactional.

e.g, your domain:

class Book
{
  String title
  static constraints = {
    title(blank: false)
  }
}

your controller action:

def update = {
  def book = Book.get(params['id'])
  book.properties = params

  if(!book.hasErrors() && bookService.update(book)
  {
    ....
  }
  else
	...
}

and the BookService method:

def update(book)
{
  if(!book.hasErrors() && book.save())
  {
		//do something
  }
  else
  {
    throw new RuntimeException()
  }
	..
	..
}

When I set the Book.title to a blank String, indeed the RuntimeException was thrown. But! The blank title was still saved! How could that happen? The only thing that I could pin point is the fact that before the service was invoked, the book instance was already dirty, by setting params as it’s properties. And a session flush happened prior to the service call.

With the help of Burt Beckwith from the list, here’s a couple of solutions.

One is to either invoke all GORM methods from the service exclusively. I tried this, and it worked. But it’s very messy since you don’t have a handle on the domain object from within the controller. Unless of course it is returned by the service call.

Another solution which I eventually used is to pass the property changes to the service method. This, instead of doing the changes prior to the service method call. Retained the domain object retrieval from within the controller.

So your service should look something like:

def update(book, params)
{
  book.properties = params
  ...
}

The downside of course is that you are already passing Maps to your service methods. Hmm actually, dunno if that’s really a downside.

Bottom line is: Never pass a dirty domain object to a transactional service!

Here’s the issue ticket for this sucker.

One Response leave one →
  1. 2009 May 19
    lramos permalink

    Thanks

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS