Spring Boot + JPA2 + Hibernate - enable second level cache
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
@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.
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 :)