The idea behind the general concept of a proxy is that: use of proxy objects should be identical to the use of real object. This utopian wish is impossible to accomplish in practice, and Hibernate proxies are no exception. Hibernate proxies can cause several problems, including
- gotchas related to equals method implementation (not discussed),
- the famous LazyInitializationException (which I have promised not to discuss)
- casting problems caused by use of polymorphic associations (this is a well documented issue and I will stay away from it as well).
Probably for that reason, proxies had a rough start in Grails. The term 'proxy hell' was coined and some interesting bugs like this one: http://jira.grails.org/browse/GRAILS-2570 have been reported.
Code Examples:
This code will get me a proxy object
def id
BankAccount.withNewSession {
id = BankAccount.findByName('a1').id
}
def a1 = BankAccount.load(id)
assert simple instanceof HibernateProxy
Note, that as usual with Hibernate, I can break the above code by adding an isolated query:
BankAccount.findByName('a1') //added this line
def id
BankAccount.withNewSession {
id = BankAccount.findByName('a1').id
}
def a1 = BankAccount.load(id)
assert simple instanceof HibernateProxy //now fails
You may find this last code example somewhat unrealistic, but I hope you agree with me that it explains the intermittent aspect of how proxies work (or why they often do not work).
How proxy code typically breaks:
Sooner or later, each Grails app will need to use domain object class to do various things with it like:
- check if an object is a domain object or something else
- introspect GORM properties on a domain object
- find beans named after a domain object (for example, find BankAccountService if object is BankAccount)
entity.getClass().shortName
may give me wrong class name! And if I do forget, my code will work, only, not always.
If you are very diligent in making sure that you always use the un-proxied classes only, then rest assured that someone else is not that diligent. For example, here is a currently open Grails JIRA: http://jira.grails.org/browse/GRAILS-11630 and if you search source of, for example, various template libraries available out there you will find things like this code:
grailsApplication.getArtefact(
DomainClassArtefactHandler.TYPE,
element.getClass().name
)
which, again, I am sure was tested and works ... only not always.
How to test for proxy problems:
Integration tests may find them but few developers code integration test separating these essential test elements into separate Hibernate sessions:
- data setup (given)
- test itself (when)
- test asserts (then)
Next topic:
So far I have focused a class of Hibernate side-effects, where:
BankAccount.findByName('a1') //(1)
someOtherCode() //(2)
adding isolated query (1) changes behavior of code (2).
I want to continue discussion about side-effects, but from a somewhat different angle. In post 1, I have promised not to talk about auto-saving, I will break that promise. There are some very 'interesting' aspects of auto-saving worth examining, for example, I sometimes get DB update operation errors from issuing a finder query. I will try to examine a few of these interesting things. Things that happen as a consequence of Hibernate design decision to make a major side-effect, like SQL update or insert operation, implicit and invisible.
No comments:
Post a Comment