Spring Boot 2.0 Hibernate 5 EhCache 3 with JCache
Asked Answered
A

2

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

Agreement answered 24/5, 2019 at 14:31 Comment(9)
Are you getting any exceptions? How do you know it's not working?Romona
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.Agreement
can you try updating the database and check if it is still using the cached data or the updated one?Romona
I've updated the values, and the values were not refreshed no matter how long I waited.Agreement
Why is it returning List<PersonEntity> and not PersonEntity, isn|t the ID a primary key ?Karame
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.Mahayana
@AlexandarPetrov Sorry, that was just an example mistake. I've updated the question. @Mahayana Yes, if I set use_query_cache to false it will read everytime. If I remove @QueryHints it will read every time.Agreement
I don't know why but in my demo project all works as expected. Just run it and hit GET localhost:8080/users?name=smith. TTL for users is 1 minute. Hope it helps...Northnortheast
+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.xmlCoenurus
N
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'.
Northnortheast answered 30/5, 2019 at 14:22 Comment(4)
Do you know where this is documented? This resolved my issue.Agreement
@Agreement here.Northnortheast
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.Bamboozle
+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.xmlCoenurus
K
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

Karame answered 29/5, 2019 at 7:3 Comment(4)
I'm getting this error: Error:(23, 33) java: cannot access net.sf.ehcache.CacheManager class file for net.sf.ehcache.CacheManager not foundAgreement
I believe this is outdated and for Ehcache 2.Agreement
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.Karame
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.Karame

© 2022 - 2024 — McMap. All rights reserved.