I just finished a consulting job at a large retailer where we managed to increase the performance of a Hibernate application by perhaps two orders of magnitude with just some fairly simple changes. It really drove home to me how almost all performance problems I've ever seen can be solved by either or both of:
- appropriate session handling
- appropriate association fetching strategies
(Note that I have not yet met a serious performance problem in Hibernate 2.x that I could not solve quite quickly.)
Hibernate's Session object is a powerful abstraction that allows some extremely flexible architectural choices. Unfortunately, this flexibility comes at a cost: many people seem to stuff it up! There are three well-understood patterns for managing Hibernate sessions correctly (actually, three-and-a-half, as I've recently discovered) and three common antipatterns. The fact that the antipatterns are more common than they should be suggests a real problem with our existing documentation, and highlights the fact that /we need to get this book out!/
The main reason we've previously been unable to explain the correct ways to handle Hibernate sessions is that we simply havn't had a decent language for describing our ideas. Since we've developed this language in the process of writing the book, explanations are much easier. The key concept is the notion of an /application transaction/. An application transaction is a unit of work from the point of view of the user
; it spans multiple requests and multiple database transactions - it does, however, have a well-defined beginning and end. Even if you don't currently use this notion explicitly, you probably /do/ use it implicitly in your application.
Briefly, the three acceptable approaches are: session-per-request, session-per-request-with-detached-objects and session-per-application-transaction. A variation of the third approach is the newly-discovered session-per-application-transaction-with-flush-delayed-to-the-last-request (phew!). The three broken approaches are: session-per-operation (ie. many-sessions-per-request), session-per-user-session and session-per-application. If you are using any of these approaches, please stop.
The three acceptable approaches each have different performance and architectural implications and there is no best
solution. It is incredibly important to choose the approach that is most suitable to your particular application (this was the key to the two-orders-of-magnitude improvement described above).
Association fetching is, I think, covered quite well in our documentation, but we still sometimes see people struggling with the dreaded n+1 SELECTs problem. So let me be very clear: Hibernate /completely/ solves the n+1 SELECTs problem! However, it takes some thought and a (very) little work on the part of the user to take full advantage of this fact. We recommend that all associations be configured for lazy fetching by default. Then, for particular use cases, eager outer join fetching may be chosen by specifying a LEFT JOIN FETCH clause in a HQL query, or by calling setFetchMode() for a Criteria query. (If you are too lazy to do this work, you could even try the new batch fetching features of Hibernate 2.1. We don't recommend this less elegant approach, however.)
Less common performance problems may be fixed by using a second-level cache, or occasionally by managing flushing manually (set the session flush mode to FlushMode.NEVER and flush manually when required). However, in almost all cases, acceptable performance can be achieved by concentrating upon the two items listed above.