10

I'm trying to setup Hibernate with EhCache as the second level cache but the TTL is not working.

Here are my dependencies:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

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

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

<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
</dependency>

Here's my YAML configuration:

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        dialect: Dialect
        cache:
          use_second_level_cache: true
          region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
          use_query_cache: true
  cache:
    jcache:
      config: classpath:ehcache.xml

Here's how my Entity class is configured:

@Entity
@javax.persistence.Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class PersonEntity {
  //
}

And the JpaRepository for the entity:

public interface PersonRepository extends JpaRepository<PersonEntity, Integer> {
  @org.springframework.data.jpa.repository.QueryHints({
      @javax.persistence.QueryHint(name = "org.hibernate.cacheable", value = "true")
  })
  List<PersonEntity> findByName(String name);
}

I've configured the cache to expire in 2 seconds, but calling findByName still uses the cache (there are no SQL Queries printed after the first one).

Here's the ehcache.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://www.ehcache.org/v3">

  <cache-template name="simple">
    <expiry>
      <ttl>2</ttl>
    </expiry>
    <heap>100</heap>
  </cache-template>

  <cache alias="com.sample.PersonEntity" uses-template="simple"/>

</config>

EDIT: I've done some debugging. I've added a break point in org.ehcache.jsr107.ExpiryPolicyToEhcacheExpiry:

javax.cache.expiry.Duration duration = this.expiryPolicy.getExpiryForCreation();

This duration is INFINITE for some reason. So maybe the configuration is not set properly? I know the xml is being read because when I invalidate it (by removing heap tag for example) I get an error.

enter image description here

George
  • 2,820
  • 4
  • 29
  • 56
  • Are you getting any exceptions? How do you know it's not working? – Vishrant May 28 '19 at 18:21
  • I have added the `ehcache.xml` configuration file with a TTL of 2 seconds, but only the first method call is printing the query, meaning it's using a cache, but it's not expiring. – George May 28 '19 at 18:23
  • can you try updating the database and check if it is still using the cached data or the updated one? – Vishrant May 28 '19 at 21:40
  • I've updated the values, and the values were not refreshed no matter how long I waited. – George May 28 '19 at 21:58
  • Why is it returning List and not PersonEntity, isn|t the ID a primary key ? – Alexander Petrov May 29 '19 at 06:55
  • How do you test usage of 2nd level cache? If you disable 2nd level cache, does it read twice? Make sure not doing both reads within same Hibernate session. – Selaron May 29 '19 at 07:24
  • @AlexandarPetrov Sorry, that was just an example mistake. I've updated the question. @Selaron Yes, if I set `use_query_cache` to false it will read everytime. If I remove `@QueryHints` it will read every time. – George May 29 '19 at 14:57
  • I don't know why but in my [demo project](https://github.com/Cepr0/base-crud-rest-service) all works as expected. Just run it and hit `GET localhost:8080/users?name=smith`. TTL for users is 1 minute. Hope it helps... – Cepr0 May 30 '19 at 12:22
  • +1 - then in the original question, what is `cache.jcache.config=classpath:ehcache.xml` about. Plz take a moment to reply. I want to know the difference between above and `hibernate.javax.cache.uri=classpath:ehcache.xml` – samshers Oct 30 '22 at 16:07

2 Answers2

15

I think I found the cause of the issue - you didn't specify a location of the ehcache.xml file:

spring:
  jpa:
    properties:
      hibernate:
        javax.cache:
          provider: org.ehcache.jsr107.EhcacheCachingProvider
          uri: classpath:ehcache.xml
        cache:
          use_second_level_cache: true
          region.factory_class: jcache
          use_query_cache: true

in this case Hibernate creates a cache with default configuration. A fragment from my demo project log:

17:15:19 WARN [main] org.hibernate.orm.cache: HHH90001006: Missing cache[user] was created on-the-fly. The created cache will use a provider-specific default configuration: make sure you defined one. You can disable this warning by setting 'hibernate.javax.cache.missing_cache_strategy' to 'create'.
Cepr0
  • 28,144
  • 8
  • 75
  • 101
  • Do you know where this is documented? This resolved my issue. – George May 31 '19 at 16:55
  • @George [here](https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#caching-provider-jcache). – Cepr0 May 31 '19 at 18:59
  • Thanks for sharing these configurations. My configurations were wrong, as they were creating two different cache's with the same name and hence ttl was not working. – SPS Dec 16 '21 at 11:22
  • +1 - then in the original question, what is `cache.jcache.config=classpath:ehcache.xml` about. Plz take a moment to reply. I want to know the difference between above and `hibernate.javax.cache.uri=classpath:ehcache.xml` – samshers Oct 30 '22 at 16:08
0

When you set your @Cacheable annotation on top of your entity it creates a region where the KEY is the ID of the entity and the Value is the entity. The above mean that you will hit the cache if you access by key which is the ID. If you use spring data and findById it will hit the cache. If you create a method findByName the access will not be by key trerefore it will not hit the cache region defined by your Cacheable annotation. On the other hand it will hit the query cache, but the query cache is in entirely different region. And judging from your configuration you have not configured query cache at all.For this method to hit any cache at all you need to add it using this property:

spring:jpa:properties:hibernate:cache:use_query_cache: true

Alternativly you can specify @Cacheable on top of the repository method this way define a new region.

You can configigure default cache, this should capture the StandardQueryCahache.

<defaultCache 
    maxElementsInMemory="10000"
    eternal="false"
    timeToIdleSeconds="3600"
    timeToLiveSeconds="3600">
  </defaultCache>

In EhCache2 you can configure the standard query cache through this element:

  <cache
name="org.hibernate.cache.internal.StandardQueryCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600">

Not sure how it is in ehcache 3 though. I believe it should be the same, because the StandartQueryCache class is part of the hibernate package and not part of the ehcache package.

I also think you need to set
hibernate.javax.cache.provider = org.ehcache.jsr107.EhcacheCachingProvider

Alexander Petrov
  • 9,204
  • 31
  • 70
  • I'm getting this error: `Error:(23, 33) java: cannot access net.sf.ehcache.CacheManager class file for net.sf.ehcache.CacheManager not found` – George May 29 '19 at 15:09
  • I believe this is outdated and for Ehcache 2. – George May 29 '19 at 15:16
  • yes you are right. This is ehcach2. Try adding hibernate.javax.cache.provider = org.ehcache.jsr107.EhcacheCachingProvider There is another provider the non hibernate one, it is not clear which one the spring boot autoconfiguration is picking up by default. Might be worth setting it. – Alexander Petrov May 29 '19 at 18:35
  • I can see also that you have modified the query to be findByName. The cache regiouns are defined by ID, so the ID is the key. If you select by NAME you are not going to hit the cache region unless you annotate the method with Cacheable and then you will be effectivly create a new cache region which is different from "com.sample.PersonEntity". Change the method to be findById and see if you are hitting the cache. – Alexander Petrov May 29 '19 at 18:43