Hibernate has long had a feature known as “query caches” – you can run a query, cache the result, and thus avoid running it repeatedly. The only problem is that it doesn’t do what you think it does.
Here’s an example of what I mean by a query cache:
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") .setEntity("blogger", blogger) .setMaxResults(15) .setCacheable(true) .setCacheRegion("frontpages") .list();
(The example code has been lifted from the Hibernate on-line manual
On the surface, this sounds like it would cache the results of the query until the (separately configured) cache expires the results. And if you never change any data, that’s what happens. The catch, however, is that any change to any of the criteria fields results in the cache being invalidated.
Let’s go for a realistic example. Customers have Orders. You create a page where customers can browse their order history. This doesn’t have to be up-to-the-minute, so you put a query cache in, with a suitable expiry policy. The query would look similar to the above:
from Order order where order.customer = :customer. So far so good. In testing, you verify that, yes, the query cache works and the database is not repeatedly called with queries.
However, when this goes live, the database starts getting swamped with these queries. It’s almost like the query cache isn’t getting used at all. Why? Because every time a customer (any customer) places an order, all the cached query results get removed – even if they were for a different customer.
This explains a little throw-away line in the Hibernate manual:
“Most queries do not benefit from caching, so by default queries are not cached.”
Why do most queries not benefit from caching? Because, in order to get cache hits, you need to essentially be querying for static or nearly-static data.
BTW, the same goes for collections. If your Customer class has a collection of Orders, you can define that collection to be cacheable; it’s a separate cache from the Customer object, but it’s cacheable. However, as soon as any order is made – or even merely modified – the cache will be invalidated.
This can make one-to-many relationships insanely expensive, particularly non-lazy ones; every time the entity goes into a session, the collection will be populated – even if the entity was already in the second level cache. If that collection cache is invalidated, as it probably is, then a database query will be run to populate the collection. If these objects also have a collection cache (think Customer -> Order -> Order LineItem), you will get N queries run. Go 4 levels (SalesPerson.getRecentCustomers()?), and you get n*m queries. Ouch.
Hibernate has very flexible caching policies for entities, but no flexibility around the relationships between the entities. This is a shame, particularly for the query caches – it means that for a number of types of applications, you need to build a separate caching layer above Hibernate, simply because the query caches get wiped far too often. At the very least, the Hibernate documentation could explain this better, elaborating on exactly why “most queries do not benefit from caching”.