About two years ago, l (like many others) bought and read Hibernate In Action. It was definitely the most decisive book on the Object to Relational Mapping tool that l could find. Apart from being really well written, the book was a great reference text which could be used in the trenches to configure and use Hibernate.
Now, a few years after using Hibernate in angst, l have realised the decision to use ORM has large drawbacks; larger than l initially envisioned. Notably, the infamous N+1 selects problem can cause increased development times as developers spend a large portion of time “ironing out” the performance issues related to the ORM implied constraints.
In first appearances, the N+1 problem presented performance issues that would often cripple the JVM for memory whist fetching and hydrating objects from the database. The solution seems trivial in that defining lazy associations would restrict the loading of object until they were needed, consequently restricting the number of objects in the object graph. However, this is not the be-all-and-end-all of problems relating to N+1.
Looking back, l had a sense of victory after we performed a first pass to detail the domain associations with a view to implement a more performant database abstraction. However, as the system functionality increased, the requirements on the domain model unfolded to create more and more “scenario based” object associations. To illustrate this point, let me give a Hello World! example. If my domain object
Customer has associations to
Address then my association could be represented as:
Customer (1) ---------- (*) Order (1) --------- (*) Item | --------------(*) Address
Now, if l don’t define any lazy associations, then when l load my
Customer from the database, Hibernate will retrieve all
Addresses. Adding lazy associations to each relationship, l can now control the loading of
Addresses as l need them. Typically, this is achieved by “touching” the
Collection that l need to load from the
Customer. Simple right?
In my example, one possible “scenario” is the non-lazy one; e.g. when you load a
Customer, all associated objects are also loaded. Another scenario is the lazy one,
Order‘s and no
Addresses. I’ve described two scenarios here. Can you see anymore? I can.
Orders only (e.g. no
Item‘s). Any more? Yep,
Addresses only. l can go on and on. So, the number of possible object loading scenarios is a function of the number of associations in the domain model. Now, that can be a very big number! Exponential actually.
Given this aspect of ORM must be solved to increase the performance and scalability of the domain, how is this typically implemented? That’s the focus of this blog; N+1 leaking into the service methods.
To continue with the above example, if l create a service to retrieve
Customer‘s, l could (without considering the lazy associations) create a service named
CusomterManager with a single method named
getCustomer(int customerId). However, the service interface is certain to be non-performant as my
Customer will be loaded with
Addresses. Now, if l want to specialise my object graph that is returned, l need to add more methods to the
getCustomerWithOrdersItemsAndAddresses(int customerId), and so forth.
So, from a single method in my
CustomerManager service to four! Thats what l call an abstraction leakage. My clients are now exposed to the shortcomings of ORM and l have severely polluted my service interface.
Avoiding this service pollution is not a concern of development. This responsibility rests squarely with the application architect. Constraints applied by application architecture are the primary cause of the abstraction leakage which were mandated by the use of, say EJB. Retrieving object graphs from Stateless Session Beans will directly present the ORM shortcomings for clients to deal with. However, services deployed in, say, the Servlet container will remove the need to pollute service interfaces, but create other issues like holding onto resources (such as a database connection) for lengthy periods of time to allow the service implementations to “retrieve” the lazily loaded associations as needed.
In conclusion, living with Hibernate is costly. The semantic definitions of your interfaces will resemble the object graphs that are being fetched and returned. Ive found this to be really messy and will force unwanted constrains on otherwise simple service definitions.
Recently, l encountered a very interesting fact about using Hibernate and EJB. Our design was simple, Container Managed Transactions and a remote interface provided by EJB, Spring wired business objects, and Hibernate as our data access layer…. Read more