63

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?

Daimon
  • 3,703
  • 2
  • 28
  • 30
  • 1
    Hope you enabled caching management, in your Configuration class using `@EnableCaching` or in xml file using ``. – Arpit Aggarwal Jul 23 '15 at 11:22
  • Though it's only used for Spring Caching - i wan to use JPA2 Caching (update question to indicate I'm using `@javax.persistence.Cacheable`) on class level – Daimon Jul 23 '15 at 11:45
  • Hello I followed all steps mentioned by you and others but still, I am not able to enable the 2nd level caching in hibernate I am using spring boot and hibernate 5.4.15 final jar and in spring boot it is giving me ehcache 2.10.6 jar. I am getting following warning "HHH020100: The Ehcache second-level cache provider for Hibernate is deprecated." – Piyush Yawalkar May 12 '20 at 16:31

6 Answers6

108

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
LeO
  • 4,238
  • 4
  • 48
  • 88
Michał Stochmal
  • 5,895
  • 4
  • 36
  • 44
  • 1
    how to add `time to live` for the cached entity? Also, does the second level cache evicts or delete itself by default, or does it maintain availability through the application uptime? – greperror Feb 06 '20 at 11:48
  • Are both `hibernate-ehcache` AND `hibernate-jcache` required in the `pom.xml` or not? I'm confused cause I'm using Spring Boot 2.2 + Hibernate >6 and EhCache 3. I'm unclear if they would be used alternatively or as an replacement. Especially since other blogs only mention the first and don't talk about the `hibernate-jcache`. E.g.http://www.ehcache.org/documentation/2.8/integrations/hibernate.html – LeO Feb 14 '20 at 14:12
  • 4
    @LeO You're mixing two things. There is `hibernate-echache-2` and `hibernate-ehcache-3`. The first one (2) was standalone cache implementation which is now obsolete. The second one (3) is an implementation of JSR-107 API (also called jcache). If you're using ehcache ver. 3 both dependencies (hibernate-jcache and hibernate-ehcache-3) are needed. – Michał Stochmal Feb 14 '20 at 14:35
  • 2
    @greperror second level cache evicts itself every time entity changes. In order to change `time to live` you'll need to provide custom cacheManager bean via `@Bean public CacheManager cacheManager()`. Ehcache docs about configuration of cache expiration: https://www.ehcache.org/documentation/3.8/expiry – Michał Stochmal Feb 14 '20 at 14:53
  • Uhu, I see... Perhaps the wording should be even more explicit about it. Thx. One q left: why does `EhCache` know that it's used in `JCache` mode and not in `native`? Is it due to the `hibernate-jcache` package? – LeO Feb 14 '20 at 16:14
  • 2
    @LeO technically hibernate-ehcache (3) is using `javax.cache:cache-api` artifact at provided scope, so you must add this artifact manually at `compile` scope. Actually `hibernate-jcache` has this dependency in `compiled` scope + some bonus logger and `hibernate-core` dependencies. Just look at these maven artifacts: https://mvnrepository.com/artifact/org.ehcache/ehcache/3.8.1, https://mvnrepository.com/artifact/org.hibernate/hibernate-jcache/5.4.12.Final – Michał Stochmal Feb 14 '20 at 23:07
  • @greperror you can have a xml file in order to configure your caches, lets say 'time to live' or 'time to idle'. [Here](https://discourse.hibernate.org/t/using-hibernate-with-ehcache-and-jcache/1943) you can see a good example. And you can refer to it as follows: `spring: jpa: properties: hibernate: javax: cache: uri: classpath:jcache.xml` – Alfredo Carrillo Mar 19 '20 at 18:39
  • Thanks! This is great example and helped me with the initial setup. Just a small hint: not setting sharedCache explicitly is OK. By default un-annotated entities won't be cached. And the moment you use Hibernate's Cache annotation, that setting is mostly ignored: https://github.com/hibernate/hibernate-orm/blob/5.4.14/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java#L571 – zeratul021 Apr 09 '20 at 11:15
  • Latest spring boot and hibernate which is by default > 5.3 will require org.hibernate.cache.jcache.JCacheRegionFactory to be the "factory_class". It solved my problems of getting this work. – Indrajit Kanjilal Jan 28 '22 at 13:12
  • I have added all as mentioned but My query run every time. How would I know Cache hit or Miss? I am using spring boot 2.7 and ecache 3.10 and jcache 5.6.15 – Mubasher Apr 17 '23 at 13:08
40

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 :)

Daimon
  • 3,703
  • 2
  • 28
  • 30
  • 1
    Thx Daimon, and for anyone come this far, it is worth noting that you need both configuration from the question, in addition to the configuration of this answer. – Xiangyu Jan 05 '17 at 08:22
  • 34
    It is preferable to set `spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE` because only then will you honour your `@javax.persistence.Cacheable` annotations. – Michael Piefel Feb 13 '17 at 10:57
  • I resolved the problem by setting this property : hibernate.cache.region.factory_class – Pasha Feb 18 '18 at 08:15
  • I am getting Error with spring boot 1.5.9.RELEASE : An exception occurred while running. null: InvocationTargetException: Error creating bean with name 'entityManagerFactory' defined in class path resource [o rg/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is java. lang.IllegalArgumentException: No enum constant javax.persistence.SharedCacheMode.javax.persistence.SharedCacheMode.ALL – Nitul Apr 11 '18 at 13:06
  • As far as I see, there is no need to explicitly set ```hibernate.cache.region.factory_class``` and ```hibernate.cache.region.use_second_level_cache``` because ```org.hibernate.cache.internal.RegionFactoryInitiator``` will automatically do this if there is only one ```RegionFactory``` implementation – Yevhenii Smyrnov May 07 '21 at 07:45
9

@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.

Community
  • 1
  • 1
GeBeater
  • 141
  • 1
  • 5
  • Nope it was not the case. spring.jpa.properties.javax.persistence.sharedCache.mode has to be set explicitely. Whether it's ALL or different setting that's another story and not related to this problem itself – Daimon Apr 24 '16 at 17:56
  • 1
    To add my two cents: With Spring Boot 1.4 and Ehcache and Hibernate 5.1 you really need at least to set the region factory and the shared-cache-mode. Even if `ENABLE_SELECTIVE` is documented to be the default, I needed to set it to this value exlicitly. – Michael Piefel Feb 13 '17 at 10:56
2

Did you add

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

on the class you want to cache?

m1416
  • 1,049
  • 12
  • 22
0

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.

crv
  • 11
  • 3
  • 1
    While it is advisable to have the `ehcache.xml`, it is by no means necessary. Ehcache will use the default cache configuration which gives you 10.000 elements and a TTL of 120s – this is not tuned, but is a starting point that’s good enough for many. Note also that having the `ehcache.xml` is not enough, you must also define the proper caches to get rid of all the warnings. – Michael Piefel Feb 13 '17 at 10:54
-2

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.

Arun Raaj
  • 1,762
  • 1
  • 21
  • 20
  • please provide at least part of the detail from the link here as the link can get broken in the future. – Jef May 25 '21 at 14:09
  • The link provided describes how to configure spring boot cache, not Hibernate. They are different things. – Rod Jan 05 '22 at 13:55