How does Hibernate Query Cache work

Introduction

Now that I covered both Entity and Collection caching, it’s time to investigate how Query Caching works.

The Query Cache is strictly related to Entities, and it draws an association between a search criterion and the Entities fulfilling that specific query filter. Like other Hibernate features, the Query Cache is not as trivial as one might think.

Entity model

For our test cases, we are going to use the following domain model:

The Post entity has a many-to-one association to an Author and both entities are stored in the second-level cache.

Enabling query cache

The Query Cache is disabled by default, and to activate it, we need to supply the following Hibernate property:

The parameter is stored in the cache entry key. The cache entry value first element is always the result set fetching timestamp. The following elements are the entity identifiers that were returned by this query.

Consistency

HQL/JPQL Query Invalidation

Hibernate second-level cache favors strong-consistency, and the Query Cache is no different. Like with flushing, the Query Cache can invalidate its entries whenever the associated tablespace changes. Every time we persist/remove/update an Entity, all Query Cache entries using that particular table will get invalidated.

The Query Cache entry is not removed, but its associated timestamp is updated

The Query Cache always inspects an entry key timestamp, and it skips reading its value if the key timestamp is newer than the result set loading timestamp

If the current Session reruns this query, the result will be cached once more

The current database transaction commits

The actual invalidation takes place and the table space timestamp is updated according to the transaction commit timestamp

Native Query Invalidation

As I previously stated, native queries leave Hibernate in the dark, as it cannot know which tables the native query might modify eventually. In the following test, we are going to update the Author table, while checking the impact it has on the current Post Query Cache:

Only the provided table space was invalidated, leaving the Post Query Cache untouched. Mixing native queries and Query Caching is possible, but it requires a little bit of diligence.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

The Query Cache can boost the application performance for frequently executed entity queries, but it’s not a free ride. It’s susceptible to consistency issues and without a proper memory management control mechanism, it can easily grow quite large.

Post navigation

13 thoughts on “How does Hibernate Query Cache work”

I have a multi-tenant application and am using hibernate filters to achieve the multi-tenancy. As findOne and similar methods do not support filters, I am not using them and hence the L1 cache is not used in the application at all.

As a workaround, I was trying to use the Query cache only for findById queries (internally it will have a where attached by the hibernate filters) but seems like it requires L2 cache setup as well.

Is there any way I can use query cache with L1 cache? I needed this behaviour because I intend to do only a session level caching.

The query cache cannot work for the L1 cache since L1 always take precedence over anything in L2 or the DB. Just override the findOne Spring method to pass the tenant identifier. It’s as simple as that.

Vlad, I read the chapter and some more details mentioned in the book about caching. What I understood was that Persistence Context will store the entity, but it will do this when entityManager.find(Post.class, postId) is triggered.

If I override the Spring’s findOne method and start passing tenant identifier in the query, I will have to do it using entityManager.createQuery or equivalent which in turn will not save things in PersistenceContext (I did a POC on this and every call to the findOne method in the same session led to a db query)

You can customize the way the Session is built to pass the tenant identifier from a ThreadLocal library. It might be that Spring offers some integration for that. Nevertheless, it’s easy to build it via AOP too.

Hi Vlad,
I found that your issue HHH-12430 was closed as duplicate. The original issue HHH-1523 is marked as open since 2006. In the original issue details only Hibernate versions 3.0.5 and 3.1 are pointed to
be affected. This is misleading and we know that this is not true because it is not working in 5.2.16 and 5.0.12 as we checked.

Could you please append 5.x versions to the affected versions in HHH-1523 issue? Maybe this could bring more attention to this old issue and will result in a faster fix…

I appended the affected versions. I’m not sure how easily this fix is going to be since the entity query cache will always store just the ids of the entities being fetched but not associated fetches. That’s also probably why it wasn’t fixed yet.