Sunday, 6 May 2012

Hibernate Caching - Part III - Caches in practice

This part is about the cache strategies. In a couple of tests I'm showing the behavior of Hibernate with different cache strategies.

About the test:

You can skip this if you have read the Part II.

  • I tried to create similar tests for each strategy, but they are slightly different.
  • The tests are simple JUnit test - anybody easily can run them
  • The database is an in-memory H2 database generated at each test and filled up with 90 record of each type.
  • The database isolation level is Repeatable read.

  • The assertion checks the number of db queries, number of Query cache hits, number of 2nd level cache hits and the number of 2nd level cache puts IN THE LAST OPERATION.

Example:

        assertStat(em1, 1, 2, 3, 4);

In this case: 1 query run on the database, 2 query were found in the query cache, 3 entity were found in the 2nd level cache and 4 entities were added to it.

Comment:
Sorry, but I won't bother with the configuration. You can download the example application and check my configuration and/or here are a couple of links about the configuration of hibernate caching:

Caches in practice

1. First level cache is not shared but it's always returns the same entity.

See FirstLevelCacheTest.java

2. Second level cache is shared between session but the different session return different instances.

See SecondLevelCache.java

    @Test
    public void sharedSecondLevelCache() {

        final List<ReadOnlyEntity> entitiesEm1 = getROEntities(em1, "EM1");

        printStat(em1, "EM1");
        {
            // Entities put into 2nd level cache.
            final Statistics stats = getStatistics(em1);
            assertLong("2nd level hit count", 0, stats.getSecondLevelCacheHitCount());
            assertLong("2d level put count",  90, stats.getSecondLevelCachePutCount());
        }

        final EntityManager em2 = emf.createEntityManager();
        final List<ReadOnlyEntity> entitiesEm2 = getROEntities(em2, "EM1");

        printStat(em2, "EM2");
        {
            // Entities have been pulled from 2nd level cache.
            final Statistics stats = getStatistics(em2);
            assertLong("2nd level hit count", 90, stats.getSecondLevelCacheHitCount());
            assertLong("2d level put count",  90, stats.getSecondLevelCachePutCount());
        }

        assertEquals(entitiesEm1.size(), entitiesEm2.size());

        // Different instances in the results
        assertFalse(entitiesEm1.equals(entitiesEm2));

    }

3. The second level cache is not queryable.

The second level cache just stores the values, it can't run ANY queries. The responsiblity to run a query is always the db's. (otherwise the hibernate became a DB over the second level cache).

See SecondLevelCache.java

In this test I'm retrieving all the ReadOnlyEntity-s, and put them into the 2nd level cache. After it a second query tries to find one of them with a JPQL expression but Hibernate won't use 2nd level cache it is forwarding the query to the database.

    @Test
    public void noQueryable() {
        final List<ReadOnlyEntity> entitiesEm1 = getROEntities(em1, "EM1");

        // Entities have put into 2nd level cache.
        printStat(em1, "EM1");
        assertStat(em1, 1, 0, 0, 90);

        final EntityManager em3 = emf.createEntityManager();
        final ReadOnlyEntity em3Entity = (ReadOnlyEntity) em3.createQuery("select e from ReadOnlyEntity e where e.id = " + entitiesEm1.get(0).getId()).getSingleResult();
        assertNotNull(em3Entity);
        printStat(em3, "EM3");
        // The 2nd level cache hit won't increase 
        assertStat(em1, 1, 0, 0, 0);
        em3.close();
    }

4. The second level cache is able to find entities.

The find (get entity by primary key) is can use the 2nd level cache.

See SecondLevelCache.java

In this test I'm retrieving all the ReadOnlyEntity-s, and put them into the 2nd level cache. After it using the find function, the hibernate is able to pick up the proper entity from the 2nd level cache.

    @Test
    public void findable() {
        final List<ReadOnlyEntity> entitiesEm1 = getROEntities(em1, "EM1");

        printStat(em1, "EM1");
        // Entities have put into 2nd level cache.
        assertStat(em1, 1, 0, 0, 90);

        final EntityManager em2 = emf.createEntityManager();
        final ReadOnlyEntity em2Entity = em2.find(ReadOnlyEntity.class, entitiesEm1.get(0).getId());
        assertNotNull(em2Entity);
        printStat(em2, "EM2");
        // The entity has been found in the 2nd level cache. 
        assertStat(em1, 0, 0, 1, 0);
        em2.close();

    }

5. The query cache without second level cache.

The query cache without 2nd level cache sometimes less efficient than a not cached query. How can we remove the cached query?

See QueryCacheTest.java

Using Query cache without 2nd level cache can cause a huge number of database hit because the query cache result contains only the primary keys and the entities will be picked up from the database one-by-one.

    @Test
    public void withoutSecondLevelCacheTest() {
        final EntityManager em1 = emf.createEntityManager();
        //Number of the insert statements
        final List<NoCacheEntity> entitiesNoChacheEm1 = getNoCacheEntities(em1, "EM1");

        printStat(em1, "EM1");
        // Entities haven't been added to 2nd level cache.
        assertStat(em1, 1, 0, 0, 0);
        //One select statement added
        em1.close();

        final EntityManager em2 = emf.createEntityManager();
        final List<NoCacheEntity> entitiesNoChacheEm2 = getNoCacheEntities(em2, "EM1");

        printStat(em2, "EM2");
        // Entities haven't been added to 2nd level cache.
        assertStat(em2, 90, 1, 0, 0);

        assertEquals(entitiesNoChacheEm1.size(), entitiesNoChacheEm2.size());
        em2.close();

    }

6. The second level cache without query cache.

Without query cache the hibernate will run the queries on the database. Because the queries run on the database, it looks like, there is no point to use 2nd level cache. There is only one case when you probably should use it, if you have to retrieve entities by their ids.

7. If you use regularly written entities, be careful with the query cache.

You can understand this point if you read the Part II.

6. Clean up the query caches.

Sometimes it worth to clean the caches instead of rebuilding the SessionFactory/EntityManagerFactory. (for example: tests)

No comments:

Post a Comment