Introduction to Hibernate Caching
When you think about Hibernate, the first thing that comes to mind is Hibernate’s ORM which enables you to use your OOP structured classes as database entities. Furthermore, Hibernate provides a multilevel caching mechanism, which is the actual topic of this blog post.
If this blog post was a tutorial, we could name it “How to speed up your application?”
Levels of cache
First level cache
First level cache is a session cache, which comes configured out of the box and can’t be disabled. All SQL statements must go through the first level cache.
When you make multiple SQL UPDATE statements in one session, the first level cache will try to postpone making the updates in the database until you close the session or commit the transaction manually. This way cache reduces the number of SQL statements by a huge amount. Once the session is closed, all the cached data is lost and can’t be reused by other sessions.
For example, after running this chunk of code:
User user = entityManager .createQuery("select u from users u where u.username ='johndoe'", User.class) .getSingleResult(); user.setFirstName("John"); entityManager.persist(user); user.setLastName("Doe"); entityManager.persist(user); return user;
Hibernate will combine all the changes made in this session and create only one SQL UPDATE statement when the session is closed, as shown below.
Hibernate: select user0_.id as id1_0_, user0_.created_at as created_2_0_, user0_.updated_at as updated_3_0_, user0_.email as email4_0_, user0_.first_name as first_name5_0_, user0_.last_name as last_name6_0_ from users user0_ where user0_.username=? Hibernate: update users set created_at=?, updated_at=?, email=?, first_name=?, last_name=? where id=?
After the session is closed, the cache is invalidated and cleared.
Second level cache
Since the first level cache is a session cache, its cached data can be used in that session only. If at the same time another client requires the same data, he will need to fetch the data from the database. This is where second level cache comes in handy. Second level Cache is an optional caching mechanism which allows caching objects across sessions and in order to work, some configuration is needed.
Query level cache
Query level cache is a key-value in memory storage which for keys uses SELECT queries and for values it uses identifiers of each row that is returned for the specified SELECT query. The main goal of this cache is not to speed up the retrieval of entities from cache but to speed up the querying time, that is why Hibernate does not cache the actual results of the query, but the indexes of rows which will be returned when the query is run.
Query level cache is especially helpful when you have some queries which take long to complete and are run often. In order for query level cache to work, queries must be identical. The slightest change of the SELECT query will result in results not found in cache and Hibernate will need to query the database for the results. For query level cache to work, slight configuration is needed.
Configuration
The first part of configuration is selecting a cache provider. You can choose the provider which suits your project the best from the list of six providers which Hibernate supports. We are going to use EhCache, since it meets the expectations of most applications.
To configure the provider, you should add the following line to your persistence.xml.
<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property>
After choosing the provider, we are only one step away from having a fully functional Hibernate query cache implemented.
The last step is adding annotations to our entity to make it cacheable. When adding annotations to the entity, we have to specify which concurrency strategy we are going to use. Choosing the concurrency strategy depends a lot from the type of entity you are caching. Is the data going to be changed a lot and is it going to be a problem if sometimes you get stale data from the cache? If stale data is not going to be a problem, you should go with the NONSTRICT_READ_WRITE strategy, and if you can’t tolerate stale data, the best choice for you is the READ_WRITE strategy, which guarantees consistency.
Annotations are added to the entity class definition, like this:
package models.entities; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import javax.persistence.Cacheable; import javax.persistence.Entity; @Entity @Cacheable @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class Employee extends BaseModel { private String username; private String email; private String firstName; private String lastName; }
And just like that, your cache is ready.
May your queries be quick and your data safe. Good luck!
“Hibernate Caching” Tech Bite was brought to you by Asmir Ljumić, Software Engineer at Atlantbh.
Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.