Hibernate lazy-load application design
I tend to use Hibernate in combination with Spring framework and it's declarative transaction demarcation capabilities (e.g., @Transactional).
As we all known, hibernate tries to be as non-invasive and as transparent as possible, however this proves a bit more challenging when employing lazy-loaded
relationships.
I see a number of design alternatives with different levels of transparency.
- Make relationships not lazy-loaded (e.g.,
fetchType=FetchType.EAGER)
- This vioalites the entire idea of lazy loading ..
- Initialize collections using
Hibernate.initialize(proxyObj);
- This implies relatively high-coupling to the DAO
- Although we can define an interface with
initialize
, other implementations are not guaranteed to provide any equivalent.
- Add transaction behaviour to the persistent
Model
objects themselves (using either dynamic proxy or@Transactional
)- I've not tried the dynamic proxy approach, although I never seemed to get @Transactional working on the persistent objects themselves. Probably due to that hibernate is operation on a proxy to bein with.
- Loss of control when transactions are actually taking place
- Provide both lazy/non-lazy API, e.g,
loadData()
andloadDataWithDeps()
- Forces the application to know when to employ which routine, again tight coupling
- Method overflow,
loadDataWithA()
, ....,loadDataWithX()
- Force lookup for dependencies, e.g., by only providing
byId()
operations- Requires alot of non-object oriented routines, e.g.,
findZzzById(zid)
, and thengetYyyIds(zid)
instead ofz.getY()
- It can be useful to fetch each object in a collection one-by-one if there's a large processing overhead between the transactions.
- Requires alot of non-object oriented routines, e.g.,
- Make part of the application @Transactional instead of only the DAO
- Possible considerations of nested transactions
- Requires routines adapted for transaction management (e.g., suffiently small)
- Small programmatic impact, although might result in large transactions
- Provide the DAO with dynamic fetch profiles, e.g.,
loadData(id, fetchProfile);
- Applications must know which profile to use when
- AoP type of transactions, e.g., intercept operations and perform transactions when necessary
- Requires byte-code manipulation or proxy usage
- Loss of control when transactions are performed
- Black magic, as always :)
Did I miss any option?
Which is your preferred approach when trying to minimize the impact of lazy-loaded
relationships in your application design?
(Oh, and sorry for WoT)
Solution 1:
As we all known, hibernate tries to be as non-invasive and as transparent as possible
I would say the initial assumption is wrong. Transaparent persistence is a myth, since application always should take care of entity lifecycle and of size of object graph being loaded.
Note that Hibernate can't read thoughts, therefore if you know that you need a particular set of dependencies for a particular operation, you need to express your intentions to Hibernate somehow.
From this point of view, solutions that express these intentions explicitly (namely, 2, 4 and 7) look reasonable and don't suffer from the lack of transparency.
Solution 2:
I am not sure which problem (caused by lazyness) you're hinting to, but for me the biggest pain is to avoid losing session context in my own application caches. Typical case:
- object
foo
is loaded and put into a map; - another thread takes this object from the map and calls
foo.getBar()
(something that was never called before and is lazy evaluated); - boom!
So, to address this we have a number of rules:
- wrap sessions as transparently as possible (e.g.
OpenSessionInViewFilter
for webapps); - have common API for threads/thread pools where db session bind/unbind is done somewhere high in the hierarchy (wrapped in
try/finally
) so subclasses don't have to think about it; - when passing objects between threads, pass IDs instead of objects themselves. Receiving thread can load object if it needs to;
- when caching objects, never cache objects but their ids. Have an abstract method in your DAO or manager class to load the object from 2nd level Hibernate cache when you know the ID. The cost of retrieving objects from 2nd level Hibernate cache is still far cheaper than going to DB.
This, as you can see, is indeed nowhere close to non-invasive and transparent. But the cost is still bearable, to compare with the price I'd have to pay for eager loading. The problem with latter is that sometimes it leads to the butterfly effect when loading single referenced object, let alone a collection of entities. Memory consumption, CPU usage and latency to mention the least are also far worse, so I guess I can live with it.
Solution 3:
A very common pattern is to use OpenEntityManagerInViewFilter if you're building a web application.
If you're building a service, I would open the TX on the public method of the service, rather than on the DAOs, as very often a method requires to get or update several entities.
This will solve any "Lazy Load exception". If you need something more advanced for performance tuning, I think fetch profiles is the way to go.