Spring Boot + JPA2 + Hibernate - enable second level cache

HibernateSpring BootJpa 2.0Second Level-Cache

Hibernate Problem Overview


I'm using Spring Boot 1.2.5 with JPA2 to annotate entities (and hibernate as underlaying JPA implementation).

I wanted to use second level cache in that setup, so entities were annotated with @javax.persistence.Cacheable

I also added following in application.properties:

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory

During bootup hibernate complained about lack of EhCacheRegionFactory so I also added this to pom:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>

But still queries like entityManager.find(Clazz.class, pk) are firing DB query instead of using cached data.

Any idea what is missing?

Hibernate Solutions


Solution 1 - Hibernate

To sum everything (L2 cache and query cache) up:

The first thing to do is to add cache provider (I recommend using EhCache) to your classpath.

Hibernate < 5.3

Add the hibernate-ehcache dependency. This library contains EhCache 2 which is now discontinued.

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>your_hibernate_version</version>
</dependency>

Hibernate >=5.3

In newer versions of Hibernate caches implementing JSR-107 (JCache) API should be used. So there're 2 dependencies needed - one for JSR-107 API and the second one for the actual JCache implementation (EhCache 3).

<dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-jcache</artifactId>
     <version>your_hibernate_version</version>
</dependency>

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.6.3</version>
    <scope>runtime</scope>
</dependency>

Now let's move on to application.properties/yml file:

spring:
  jpa:
    #optional - show SQL statements in console. 
    show-sql: true 
    properties:
      javax:
        persistence:
          sharedCache: 
		    #required - enable selective caching mode - only entities with @Cacheable annotation will use L2 cache.
            mode: ENABLE_SELECTIVE 
      hibernate:
	    #optional - enable SQL statements formatting.
        format_sql: true 
		#optional - generate statistics to check if L2/query cache is actually being used.
        generate_statistics: true
        cache:
		  #required - turn on L2 cache.
          use_second_level_cache: true
		  #optional - turn on query cache.
          use_query_cache: true 
          region:
		    #required - classpath to cache region factory.
            factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory 

For EhCache 3 (or Hibernate >=5.3) this region factory should be used:

factory_class: org.hibernate.cache.jcache.JCacheRegionFactory

You can also enable TRACE level logging for Hibernate to verify your code and configuration:

logging:
  level:
    org:
      hibernate:
        type: trace

Now let's move on to the code. To enable L2 caching on your entity you need to add those two annotations:

@javax.persistence.Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) //Provide cache strategy.
public class MyEntity {
  ...
}

Note - if you want to cache your @OneToMany or @ManyToOne relation - add @Cache annotation over this field as well.

And to enable query cache in your spring-data-jpa repository you need to add proper QueryHint.

public class MyEntityRepository implements JpaRepository<MyEntity, Long> {

  @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, value = "true"))
  List<MyEntity> findBySomething(String something);

}

Now verify via logs if your query is executed only once and remember to turn off all the debug stuff - now you're done.

Note 2 - you can also define missing cache strategy as create if you want to stay with defaults without getting warnings in your logs:

spring:
  jpa:
    properties:
      hibernate:
        javax:
          cache:
            missing_cache_strategy: create

Solution 2 - Hibernate

Well after some more digging here's what I was missing in application.properties:

spring.jpa.properties.javax.persistence.sharedCache.mode=ALL

Hope it helps someone :)

Solution 3 - Hibernate

@Daimon I am not really sure, whether

spring.jpa.properties.javax.persistence.sharedCache.mode=ALL

is the best decision.

Quoted from Hibernate 20.2.1. Cache mappings documentation section

> By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode element in your persistence.xml file or by using the javax.persistence.sharedCache.mode property in your configuration.

whereas

> ENABLE_SELECTIVE (Default and recommended value): entities are not cached unless explicitly marked as cacheable.

So, could it be, that you have not annotated all affected entities with @javax.persistence.Cacheable or rather @org.hibernate.annotations.Cache ? This could lead to the affect, that the Query Cache tried to look up the affected entities in the Second Level Cache without success and then started to fetch each entity by a single select.

Solution 4 - Hibernate

Did you add

@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY) 

on the class you want to cache?

Solution 5 - Hibernate

You should have an ehcache.xml file in your classpath. The file should contains at least the default cache strategy. For easier debuging, make it eternal to be sure entities are not evicted from cache :

ehcache.xml:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:noNamespaceSchemaLocation="ehcache.xsd"
  Name="CacheManager" 
  maxBytesLocalHeap="250m">

<defaultCache eternal="true"
...
/>

<cache name="org.hibernate.cache.internal.StandardQueryCache"
       eternal="true"
...
/>

To ensure that all is ok, you should have the following log during your application startup :

Could not find a specific ehcache configuration for cache named [com.yourcompany.YourClass]; Using defaults.

That means that your entity cache annotation have been correctly readed and default cache will be used.

If you test with entityManager.find(Clazz.class, pk) that's not envolve the query cache, but just the entity cache. Query cache is used for queries (em.createQuery(...) and for relations ship

Also, I use org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory, but I don't know wich is better.

Solution 6 - Hibernate

You can use third party cache provider, among JCache, Ehcache, Gvava Cache, Hazelcast Cache, Caffeine Cache.

Please refer this answer on Quora to know how to enable and configure the second level cache in Spring boot.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaimonView Question on Stackoverflow
Solution 1 - HibernateMichał StochmalView Answer on Stackoverflow
Solution 2 - HibernateDaimonView Answer on Stackoverflow
Solution 3 - HibernateGeBeaterView Answer on Stackoverflow
Solution 4 - Hibernatem1416View Answer on Stackoverflow
Solution 5 - HibernatecrvView Answer on Stackoverflow
Solution 6 - HibernateArun RaajView Answer on Stackoverflow