Thursday, April 7, 2011

Accessing the identifier of a lazy loaded association in grails without another database call

I ran into a bit of a weird scenario recently with grails. I have a couple of classes similar to those below:

class Foo {
Bar bar
}

class Bar {
static hasMany = [foos: Foo]
}


Essentially, I have a many to one relationship. Now, consider the following code:

Foo foo = Foo.get(1)
Bar bar = foo.bar


Assuming that there is actually a bar set on this particular foo, what will you get?

You'll get the actual Bar, completely instantiated. Now, imagine the exact same scenario in normal java with the exact same equivalent hibernate settings via annotations. (I'm too lazy to model what that would look like) What would you get back for a similar call? You would get back a proxy! Then, if you called something on the proxy, hibernate would go and fetch the actual object for you.

Gorm unwraps the proxy when the object itself is called via a property.

If you take a look at the HibernatePluginSupport class, which configures all of the dynamic Gorm methods (and the first place I go when I want to see how a particular method is actually working) you will see this in action:

static final LAZY_PROPERTY_HANDLER = { String propertyName ->
def propertyValue = PropertyUtils.getProperty(delegate, propertyName)
if (propertyValue instanceof HibernateProxy) {
return GrailsHibernateUtil.unwrapProxy(propertyValue)
}
return propertyValue
}

/**
* This method overrides a getter on a property that is a Hibernate proxy
* in order to make sure the initialized object is returned hence avoiding
* Hibernate proxy hell
*/
static void handleLazyProxy(GrailsDomainClass domainClass,
GrailsDomainClassProperty property) {
String propertyName = property.name
def getterName = GrailsClassUtils.getGetterName(propertyName)
def setterName = GrailsClassUtils.getSetterName(propertyName)
domainClass.metaClass."${getterName}" =
LAZY_PROPERTY_HANDLER.curry(propertyName)
domainClass.metaClass."${setterName}" = {
PropertyUtils.setProperty(delegate, propertyName, it)
}

for (GrailsDomainClass sub in domainClass.subClasses) {
handleLazyProxy(sub, sub.getPropertyByName(property.name))
}
}
So, anytime you access your properties, the above code is going to unwrap it.

What whoever wrote this is trying to avoid is the 'proxy hell' mentioned in the comment. The hibernate docs have a good explanation of it:

http://docs.jboss.org/hibernate/core/3.3/reference/en/html/performance.html#performance-fetching-lazy

As does the Grails docs:

http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.5.2.8%20Eager%20and%20Lazy%20Fetching

They're more eagerly fetching the lazy relationships than Hibernate does to avoid some of the instanceof check issues outlined in the above links.

Now, back to my issue. What happens if you want to obtain the identifier of your relationship without actually resulting in a database call to get it? The proxy has the identifier already, that's how it loads it up when you call it. In pure hibernate there's a few ways to do it:
  1. Session.getIdentifier
  2. entity.getId() (assuming you are using property based configuration)
  3. Cast it to a HibernateProxy, get the LazyLoadInitializer and get the Identifier from it
The problem with all of these solutions is that you have to get around the Gorm code above that unwraps any properties when you call a getter. You can't just add setBar and getBar. However, you can add another method that can get 'raw' access to the field. And before anyone posts a comment about it you can't use load either because it requires the id, and that's the whole point of this exercise (getting the id that is).

I didn't find this out until I had wasted quite a few hours, but apparently this was a known issue: http://jira.grails.org/browse/GRAILS-2570 I never saw this when googling for it originally, but you can essentially say:

foo.barId


And you will get the id of the bar without the proxy being unwrapped. I'm not sure this is referenced anywhere in the docs though.

10 comments:

Amit said...

Thanks for posting this!

Manuel said...

Great post, thanks! Already bookmarked this for future reference!

timbonicus said...

Thanks for putting this information out there, you saved me a few hours of frustration!

mony hussein said...

شركة كشف تسريبات الخزانات بمكة
شركة كشف تسربات المياه بالرياض
شركة عزل اسطح بالرياض
شركة رش مبيدات بخميس مشيط
شركة تنظيف مجالس بمكة
رش دفان مكة
شركة مكافحة الفئران في مكة
شركة مكافحة الصراصير بمكة
شركة مكافحة بق الفراش بمكة
شركة تنظيف مساجد بمكة
تنظيف الكنب بالبخار بمكة
تنظيف ستائر بمكة
شركة تنظيف الاثاث بمكة
شركة تخزين الاثاث بمكة
شركات رش حشرات بمكة
شركة رش المبيدات بمكة

Vahid Pazirandeh said...

THANK YOU!!!!!!!!!!!

I've been trying to optimize marshalling for Grails and remove unnecessary sql. I just want the ID, not the entire domain object!

So as a recap your example, in Groovy:
=> foo.barId works fine

I needed to use it in Java (where "foo" is still Foo class, but also GroovyObject class):
=> foo.getProperty("barId");

Vahid Pazirandeh said...

A few posts on stackoverflow as well:
* http://stackoverflow.com/questions/27084173/grails-get-domain-relationship-id-without-fetching-the-whole-object
* http://stackoverflow.com/questions/7232794/getting-the-id-of-a-one-to-many-loaded-object-without-another-trip-to-the-db-wit

Rian Hidayat said...

The information you convey very helpful at all, a lot of new things that I know after reading this article you wrote.
Cara Membeli QnC Jelly Gamat Obat Kurap Obat Rosacea Herbal Obat Lichen Planus Cara Mengatasi Peritonitis
Thank you so much for sharing this useful information, Greetings and good luck always

dong dong23 said...

michael kors outlet online
uggs for cheap
fitflops
coach factory outlet
louis vuitton handbags
louboutin pas cher
michael kors outlet
ghd hair straighteners
adidas yeezy
christian louboutin outlet
tory burch outlet
jordan 8s
michael kors outlet online
toms outlet
christian louboutin outlet
coach outlet
timberland outlet
louis vuitton borse
kate spade
uggs uk
oakley sunglasses
oakley sunglasses
louis vuitton outlet
ralph lauren polo
burberry handbags
oakley sunglasses wholesale
fitflops
jordans for sale
pandora jewelry
toms outlet
true religion outlet
moncler uk
air jordan pas cher
kate spade
ugg outlet
toms shoes
vans shoes outlet
cincinnati bengals jerseys
ray ban sunglasses outlet
kate spade handbags
20168.15wengdongdong

for IT the said...

Great step by step solution, thanks for the help!

Hibernate Online Training | Java Online Training | Java EE Online Training


Spring Hibernate Online Training | Hibernate Training in Chennai Java Training Institutes

for IT the said...

Hibernate Online Training Hibernate Online Training Hibernate Training in Chennai Hibernate Training in Chennai Java Online Training Java Online Training

Hibernate Training Institutes in ChennaiHibernate Training Institutes in Chennai Java Online Training Java Online Training Java Online Training Java Online Training