Introduction :
Caching is all about application performance optimization and it sits between your application and the database to avoid the number of database hits as many as possible to give a better performance.Hibernate provide the lots of other features, one of the major benefit of using Hibernate in large application is it’s support for caching, hence reducing database queries and better performance.
Hibernate provide caching functionality in three layers,
1) First Level Caching:
Fist level cache in hibernate is enabled by default and you do not need to do anything to get this functionality working. In fact, you can not disable it even forcefully.
Its easy to understand the first level cache if we understand the fact that it is associated with Session object. As we know session object is created on demand from session factory and it is lost, once the session is closed. Similarly, first level cache associated with session object is available only till session object is live. It is available to session object only and is not accessible to any other session object in any other part of application.
2) Second Level Caching:
The second level cache is responsible for caching objects across sessions. When this is turned on, objects will first be searched in the session if it not found then delegates searching in the cache and if they are not found, a database query will be fired. Second level cache will be used when the objects are loaded using their primary key. This includes fetching of associations. Second level cache objects are constructed and reside in different memory locations.
I will explain more on second level cache with a example.
3) Query Caching:
Query Cache is used to cache the results of a query. When the query cache is turned on, the results of the query are stored against the combination query and parameters. Every time the query is fired the cache manager checks for the combination of parameters and query. If the results are found in the cache, they are returned, otherwise a database transaction is initiated. As you can see, it is not a good idea to cache a query if it has a number of parameters, because then a single parameter can take a number of values. For each of these combinations the results are stored in the memory. This can lead to extensive memory usage.
We have to change the hibernate configuration to enable the query cache. This is done by adding the following line to the Hibernate configuration.
<property name="hibernate.cache.use_query_cache">true</property>
Read this page to find pitfalls on query second level cache.
Understanding Second level caching:
When ever hibernate session try to load an entity, the very first place it look for cached copy of entity in first level cache (i,e hibernate session).If cached copy of entity is present in first level, it is returned as result of load method.If there is no cached entity in first level cache, then second level cache is looked up for cached entity.If second level cache has cached entity, it is returned as result of load method. But, before returning the entity, it is stored in first level cache also so that next invocation to load method for entity will return the entity from first level cache itself, and there will not be need to go to second level cache again.If entity is not found in first level cache and second level cache also, then database query is executed and entity is stored in both cache levels, before returning as response of load() method.Second level cache validate itself for modified entities, if modification has been done through hibernate session APIs.
If some user or process make changes directly in database, the there is no way that second level cache update itself until “timeToLiveSeconds” duration has passed for that cache region. In this case, it is good idea to invalidate whole cache and let hibernate build its cache once again.
Let me explain second level caching with a simple app.
Maven dependencies required:
<properties> <hibernate.version>4.3.5.Final</hibernate.version> <ehcache-version>2.4.3</ehcache-version> <db.mysql.driver.version>5.1.30</db.mysql.driver.version> <slf4j-version>1.7.7</slf4j-version> </properties> <dependencies> <!-- hibernate dependencies--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>${hibernate.version}</version> </dependency> <!-- ehcache dependencies--> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>${ehcache-version}</version> </dependency> <!-- database driver dependencies--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${db.mysql.driver.version}</version> </dependency> <!-- slf4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j-version}</version> </dependency> </dependencies> <build> <finalName>hibernate-second-level-cache-example</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build>
For hibernate second level cache, they are many caching providers.Among Ehcache is popular caching framework.So we would need to add ehcache-core and hibernate-ehcache dependencies in our application. EHCache uses slf4j for logging, so I have also added slf4j for logging purposes.
Model Class:
Lets take a simple User entity java class which helps in understanding second level cache using CRUD operations on User entity.
User.java
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "user") @Entity @Table(name = "USER") public class User implements java.io.Serializable { @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "USERID", nullable = false) private Integer userid; @Column(name = "USERNAME", length = 20) private String username; @Column(name = "PASSWORD", length = 20) private String password; @Column(name = "ROLE", length = 20) private String role; @Column(name = "ACTIVE") private boolean active; //constructor,setters & getter,hashcode & equals,toString }
If you observe we use concurrency strategy in the above model,so it is time we should discuss about concurrency strategy. A concurrency strategy is a mediator which responsible for storing items of data in the cache and retrieving them from the cache. If you are going to enable a second-level cache, you will have to decide, for each persistent class and collection, which cache concurrency strategy to use.
Four Types of Concurrency Strategies:
Transactional:
Use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions,in the rare case of an update.Read-write:
Again use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions,in the rare case of an update.Nonstrict-read-write:
This strategy makes no guarantee of consistency between the cache and the database. Use this strategy if data hardly ever changes and a small likelihood of stale data is not of critical concern.Read-only:
A concurrency strategy suitable for data which never changes. Use it for reference data only.Hibernate Configuration:
<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="hibernate.connection.url"> jdbc:mysql://localhost:3306/demoDB?createDatabaseIfNotExist=true </property> <property name="hibernate.connection.username"> root </property> <property name="hibernate.connection.password"> pramati </property> <property name="hibernate.show_sql">true</property> <property name="hbm2ddl.auto">create</property> <!-- you can make it false to disable second level cache --> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- to enable query cache uncomment below property <property name="hibernate.cache.use_query_cache">true</property> --> <!-- to provide ehcache configuration file with cache configuration <property name="net.sf.ehcache.configurationResourceName">/myehcache.xml</property> --> <mapping class="com.hibernate.cache.model.User" /> </session-factory> </hibernate-configuration>
In above xml, four properties are related to caching
hibernate.cache.use_second_level_cache is true then second level caching is enabled.
hibernate.cache.use_query_cache is true then query caching is enabled.
hibernate.cache.region.factory_class :
Two values allowed to this property
The non-singleton EhCacheRegionFactory allows you to configure EHCache separately for each Hibernate instance using net.sf.ehcache.configurationResourceName property.
Where as SingletonEhCacheRegionFactory shares the same EHCache configuration among all Hibernate session factories.
net.sf.ehcache.configurationResourceName : is used to define the EHCache configuration file location, it’s an optional parameter and if it’s not present EHCache will try to locate ehcache.xml file in the application classpath.If ehcache.xml does not find in the class path it will load default ehcache-failsafe.xml which have default caching properties availble in ehcache-core.jar
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
Second Level Cache Demo :
public class HibernateSecondLevelCacheDemo { public static void main(String[] args) { SessionFactory sessionFactory = buildSessionFactory(); //enabling statistics final Statistics statistics = sessionFactory.getStatistics(); statistics.setStatisticsEnabled(true); //Adding entries to user table Session session = sessionFactory.openSession(); session.beginTransaction(); User userOne = new User(1, "user1", "user1", "user", true); User userTwo = new User(2, "user2", "user2", "user", false); session.save(userOne); session.save(userTwo); session.getTransaction().commit(); session.close(); Session sessionOne = sessionFactory.openSession(); Session sessionTwo = sessionFactory.openSession(); Transaction transactionOne = sessionOne.beginTransaction(); Transaction transactionTwo = sessionTwo.beginTransaction(); //printing initial statistics printStatistics(statistics,null,0); User user1=(User)sessionOne.load(User.class, 1); printStatistics(statistics,user1,1); User user2=(User)sessionOne.load(User.class, 1); printStatistics(statistics,user2,2); User user3=(User)sessionOne.load(User.class, 2); printStatistics(statistics,user3,3); User user4=(User)sessionTwo.load(User.class, 1); printStatistics(statistics,user4,4); User user5=(User)sessionTwo.load(User.class, 2); printStatistics(statistics,user5,5); transactionOne.commit(); transactionTwo.commit(); sessionOne.close(); sessionTwo.close(); sessionFactory.close(); } public static void printStatistics(Statistics statistics, User user , int count) { System.out.println("***************"); System.out.println("Hit : "+ count); if(user != null) System.out.println("User Details :" + user.toString()); System.out.println("Entity fetch count :" + statistics.getEntityFetchCount()); System.out.println("Second level cache hit count : "+ statistics.getSecondLevelCacheHitCount()); System.out.println("Second level cache put count : " + statistics.getSecondLevelCachePutCount()); System.out.println("Second level cache miss count : " + statistics.getSecondLevelCacheMissCount()); System.out.println("***************"); } public static SessionFactory buildSessionFactory() { URL url = HibernateSecondLevelCacheDemo.class.getClassLoader().getResource("configuration/hibernate.cfg.xml"); Configuration configuration = new Configuration(); configuration.configure(url); StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()); return configuration.buildSessionFactory(ssrb.build()); } }
Output :
Hibernate: insert into USER (ACTIVE, PASSWORD, ROLE, USERNAME) values (?, ?, ?, ?) Hibernate: insert into USER (ACTIVE, PASSWORD, ROLE, USERNAME) values (?, ?, ?, ?) *************** Hit : 0 Entity fetch count :0 Second level cache hit count : 0 Second level cache put count : 0 Second level cache miss count : 0 *************** *************** Hit : 1 Hibernate: select user0_.USERID as USERID1_0_0_, user0_.ACTIVE as ACTIVE2_0_0_, user0_.PASSWORD as PASSWORD3_0_0_, user0_.ROLE as ROLE4_0_0_, user0_.USERNAME as USERNAME5_0_0_ from USER user0_ where user0_.USERID=? User Details :User{userid=1, username='user1'} Entity fetch count :1 Second level cache hit count : 0 Second level cache put count : 1 Second level cache miss count : 1 *************** *************** Hit : 2 User Details :User{userid=1, username='user1'} Entity fetch count :1 Second level cache hit count : 0 Second level cache put count : 1 Second level cache miss count : 1 *************** *************** Hit : 3 Hibernate: select user0_.USERID as USERID1_0_0_, user0_.ACTIVE as ACTIVE2_0_0_, user0_.PASSWORD as PASSWORD3_0_0_, user0_.ROLE as ROLE4_0_0_, user0_.USERNAME as USERNAME5_0_0_ from USER user0_ where user0_.USERID=? User Details :User{userid=2, username='user2'} Entity fetch count :2 Second level cache hit count : 0 Second level cache put count : 2 Second level cache miss count : 2 *************** *************** Hit : 4 User Details :User{userid=1, username='user1'} Entity fetch count :2 Second level cache hit count : 1 Second level cache put count : 2 Second level cache miss count : 2 *************** *************** Hit : 5 User Details :User{userid=2, username='user2'} Entity fetch count :2 Second level cache hit count : 2 Second level cache put count : 2 Second level cache miss count : 2 ***************
Where as
Fetch Count : if a entity is retervied from database using sql query.
Second level cache hit count : If a entity is retervied from Second level cache.
Second level cache put count : If a entity is added into second level cache.
Second level cache miss count : If a entity tried to retervie from Second level cache,but that entity is not exist in cache.
Hit0 : we can see all are 0,it means no entity is tried to retrive from cache.
Hit1 : user1 is loading from sessionOne.First it tries to load from First level cache(Session), as it is not present in session then tries to load from Second level cache, as it's also not exist in second level cache, so "second level cache miss count" will increase by 1.Then entity will load from Database and put in first level cache & second level cache,So "second level cache put count" is increased by 1.As entry is retervied from database so fetch count will increase by 1.
Hit2 : Again User1 is requested from SessionOne.As it is there in first level cache so retervied from Session.
Hit3 : user2 is loading from sessionOne,Same as Hit1.
Hit4 : user1 is requested from sessionTwo.Then first it tries in Frist level cache,So it does not exist then tries in second level cache.As User1 is already existing in second level cache so "Second level cache hit count" increase by 1.
Hit5 : user2 is requested from sessionTwo,It same as Hit4.
That’s all about Hibernate second level caching using EHCache example, I hope it will help you in configuring EHCache in your hibernate applications and gaining better performance.
you can download source from GitHub.