Search This Blog

Tuesday 10 December 2013

The Query Cache

In the previous posts we saw the Hibernate cache system and the Cache modes. We saw the query cache is disabled by default.
The first step to getting the Query cache working is in the configuration file.
<property name="hibernate.cache.use_query_cache">true</property>
This does not means all queries will be cached.If we need a particular query to be cached:
public static void testQuery() {
      Session session = sessionFactory.openSession();
      Query query1 = session.createQuery("from BookType where name like :type");
      query1.setString("type", "%");
      query1.setCacheable(true);
      System.out.println("Query execute once");
      List<BookType> bookTypes = query1.list();
      for (BookType bookType : bookTypes) {
         System.out.println(bookType.getName());
      }
      session.close();
      
      session = sessionFactory.openSession();
      Query query2 = session.createQuery("from BookType where name like :type");
      query2.setString("type", "%");
      query2.setCacheable(true);
      System.out.println("Query execute once again...");
      bookTypes = query2.list();
      for (BookType bookType : bookTypes) {
         System.out.println(bookType.getName());
      }
      session.close();
   }
As seen above unless we specifically set a query to be cached, it does not happen. We have used two separate query objects from two different sessions to test the same query. Both have to have the setCacheable property set to true.
If I were to run the above code:
  1. When the code started execution:
    56   [main] DEBUG net.sf.ehcache.store.MemoryStore  - Initialized net.sf.ehcache
    .store.LruMemoryStore for com.object.cache.BookType
    57   [main] DEBUG net.sf.ehcache.store.LruMemoryStore  - com.object.cache.BookTy
    pe Cache: Using SpoolingLinkedHashMap implementation
    57   [main] DEBUG net.sf.ehcache.Cache  - Initialised cache: com.object.cache.Bo
    okType
    ...
    229  [main] INFO  org.hibernate.cache.UpdateTimestampsCache  - starting update t
    imestamps cache at region: org.hibernate.cache.UpdateTimestampsCache
    ...
    236  [main] DEBUG org.hibernate.cache.EhCacheProvider  - started EHCache region:
     org.hibernate.cache.StandardQueryCache
    
    As I have both secondary cache and the query cache enabled, they two were started. On enabling query caching, Hibernate sets up an additional cache, holding timestamps of the most recent updates to tables against which queries are cached. 
  2. When the first query was executed:
    Query execute once
    357  [main] DEBUG org.hibernate.cache.StandardQueryCache  - checking cached quer
    y results in region: org.hibernate.cache.StandardQueryCache
    357  [main] DEBUG org.hibernate.cache.EhCache  - key: sql: select booktype0_.ID 
    as ID0_, booktype0_.NAME as NAME0_ from BOOK_TYPE_MASTER booktype0_ where bookty
    pe0_.NAME like ?; parameters: ; named parameters: {type=%}
    357  [main] DEBUG net.sf.ehcache.store.MemoryStore  - org.hibernate.cache.Standa
    rdQueryCacheCache: org.hibernate.cache.StandardQueryCacheMemoryStore miss for sq
    l: select booktype0_.ID as ID0_, booktype0_.NAME as NAME0_ from BOOK_TYPE_MASTER
     booktype0_ where booktype0_.NAME like ?; parameters: ; named parameters: {type=
    %}
    357  [main] DEBUG net.sf.ehcache.Cache  - org.hibernate.cache.StandardQueryCache
     cache - Miss
    ...
        select
            booktype0_.ID as ID0_,
            booktype0_.NAME as NAME0_ 
        from
            BOOK_TYPE_MASTER booktype0_ 
        where
            booktype0_.NAME like ?
    ...
    383  [main] DEBUG org.hibernate.cache.ReadOnlyCache  - Caching: com.object.cache
    .BookType#1
    385  [main] DEBUG org.hibernate.cache.ReadOnlyCache  - Caching: com.object.cache
    .BookType#2
    385  [main] DEBUG org.hibernate.cache.ReadOnlyCache  - Caching: com.object.cache
    .BookType#3
    385  [main] DEBUG org.hibernate.cache.StandardQueryCache  - caching query result
    s in region: org.hibernate.cache.StandardQueryCache; timestamp=5678681352962048
    Detective
    Romance
    Autobigraphy
    
    Our BookType class is using a read only cache (second -level)
    <class name="BookType" table="BOOK_TYPE_MASTER">
       <cache usage="read-only" />
    
    When the query was executed, Hibernate checked if there is any entry in the Query Cache for this particular query. On finding none, it fired the actual query. It also added these records to the second level cache. 
  3. When the query was executed a second time:
    Query execute once again...
    387  [main] DEBUG org.hibernate.cache.StandardQueryCache  - checking cached quer
    y results in region: org.hibernate.cache.StandardQueryCache
    387  [main] DEBUG org.hibernate.cache.EhCache  - key: sql: select booktype0_.ID 
    as ID0_, booktype0_.NAME as NAME0_ from BOOK_TYPE_MASTER booktype0_ where bookty
    pe0_.NAME like ?; parameters: ; named parameters: {type=%}
    387  [main] DEBUG net.sf.ehcache.store.MemoryStore  - org.hibernate.cache.Standa
    rdQueryCacheCache: org.hibernate.cache.StandardQueryCacheMemoryStore hit for sql
    : select booktype0_.ID as ID0_, booktype0_.NAME as NAME0_ from BOOK_TYPE_MASTER 
    booktype0_ where booktype0_.NAME like ?; parameters: ; named parameters: {type=%
    }
    387  [main] DEBUG org.hibernate.cache.StandardQueryCache  - Checking query space
    s for up-to-dateness: [BOOK_TYPE_MASTER]
    387  [main] DEBUG org.hibernate.cache.EhCache  - key: BOOK_TYPE_MASTER
    387  [main] DEBUG net.sf.ehcache.store.MemoryStore  - org.hibernate.cache.Update
    TimestampsCacheCache: org.hibernate.cache.UpdateTimestampsCacheMemoryStore miss 
    for BOOK_TYPE_MASTER
    387  [main] DEBUG net.sf.ehcache.Cache  - org.hibernate.cache.UpdateTimestampsCa
    che cache - Miss
    387  [main] DEBUG org.hibernate.cache.EhCache  - Element for BOOK_TYPE_MASTER is
     null
    387  [main] DEBUG org.hibernate.cache.StandardQueryCache  - returning cached que
    ry results
    395  [main] DEBUG org.hibernate.cache.EhCache  - key: com.object.cache.BookType#1
    395  [main] DEBUG net.sf.ehcache.store.MemoryStore  - com.object.cache.BookTypeC
    ache: com.object.cache.BookTypeMemoryStore hit for com.object.cache.BookType#1
    395  [main] DEBUG org.hibernate.cache.ReadOnlyCache  - Cache hit: com.object.cac
    he.BookType#1
    ...
    Detective
    Romance
    Autobigraphy
    
    As seen here, Hibernate checked the query cache and found entries for the particular query.  It also used the UpdateTimeStamps cache to check if there were any updates against the BOOK_TYPE_MASTER table. On finding none it decided to go ahead and used the cached information. Using the entries in the query cache it retrieved the entities from the secondary cache.
What happens if I use only the Query cache and disable the second level cache for this class ?
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.cache.provider_class">
 org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_query_cache">true</property>
This fragment is from the cfg file where I have disabled second level caching.
On running the same code the logs have changed when executing the cached query a second time:
377  [main] DEBUG org.hibernate.cache.StandardQueryCache  - returning cached que
ry results
378  [main] DEBUG org.hibernate.SQL  - 
    /* load com.object.cache.BookType */ 
    select
        booktype0_.ID as ID0_0_,
        booktype0_.NAME as NAME0_0_ 
    from
        BOOK_TYPE_MASTER booktype0_ 
    where
        booktype0_.ID=?
... 
Detective
Romance
Autobigraphy
The select query was now executed for a particular Id. In fact it was executed three times for each of the records in the result.
What does this mean ?
The Query cache and the secondary level cache are meant to work hand in hand. In the above example the query cache did not cache the entire result set of BOOK_TYPE_MASTER records. Instead it only saved the identifier for each record. From these identifiers it then checks if the record is available - may be in the session cache, then in the second level cache. If not then it executes a select query for the missing record. In effect the absence of a second level cache could result in n additional queries being fired on the database - an "N+1" kind of scenario.
Visually imagined it would be something like this


In case of scalar data, the same is directly held in the Query cache. Any entities will be referred by their identifiers only.

2 comments: