javaspring-boothibernatemavenehcache

Porting to Springboot 3 / Hibernate 6 / EHCache 3


I am trying since few days to move my old project based on Springboot 2 / Hibernate 5 / EHCache 2 to the newer Java 17 / Springboot 3 / Hibernate 6 / EHCache 3.

Of course I've read a lot of documentation, tried many configurations and dependencies setup. I also debugged the libraries to figure out what was the reason of my problems.

Before posting the relevant parts of my code, I wish to list the issues I am trying to solve:

  1. The project starts, the configuration gets parsed (I see that by debugging), the EHCache listeners are registered, but when fetch data from DB, it is stored in the cache, but the listener does not fire.
  2. Despite of the 1 minute TTL, the stored items never expire.
  3. I see by debugging that the DemoEntity EHCache configuration initializes two times: from JCache before, then by Hibernate itself (this flow maybe the root cause of some of the above issues).

I realize it's not easy to answer with so little information at your disposal, but I trust you can tell me if I've made any obvious mistakes in dependency inclusion, application configuration (application.yaml), or in the code itself.

It follows the relevant code parts.

Thank you in advance!

pom.xml

<properties>
    <springboot.version>3.1.2</springboot.version>
    <ehcache.version>3.10.8</ehcache.version>
    <hibernate.version>6.2.2.Final</hibernate.version> <!-- As for the current version of springboot -->
</properties>

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
      <version>${springboot.version}</version>
  </dependency>

  <dependency>
      <groupId>org.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>${ehcache.version}</version>
      <classifier>jakarta</classifier>
  </dependency>

  <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-jcache</artifactId>
      <version>${hibernate.version}</version>
  </dependency>
</dependencies>

application.yaml

spring:
  cache:
    jcache:
      config: file:///app/config/ehcache.xml

  jpa:
    properties:
      hibernate:
        cache:
          region.factory_class: org.hibernate.cache.jcache.internal.JCacheRegionFactory
          use_second_level_cache: true
          use_query_cache: true

ehcache.xml

<config
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
    xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.10.xsd"
>
    <cache-template name="entitiesDefault">
        <expiry>
            <ttl unit="minutes">1</ttl>
        </expiry>

        <listeners>
            <listener>
                <class>api.demo.springboot.framework.persistence.JpaCacheEventListener</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>ORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EVICTED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
                <events-to-fire-on>REMOVED</events-to-fire-on>
                <events-to-fire-on>UPDATED</events-to-fire-on>
            </listener>
        </listeners>

        <resources>
            <heap unit="entries">1000</heap>
        </resources>
    </cache-template>

    <cache-template name="entitiesRelationsDefault">
        <expiry>
            <ttl unit="minutes">1</ttl>
        </expiry>

        <listeners>
            <listener>
                <class>api.demo.springboot.framework.persistence.JpaCacheEventListener</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>ORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EVICTED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
                <events-to-fire-on>REMOVED</events-to-fire-on>
                <events-to-fire-on>UPDATED</events-to-fire-on>
            </listener>
        </listeners>

        <resources>
            <heap unit="entries">1000</heap>
        </resources>
    </cache-template>

    <cache-template name="queriesDefault">
        <expiry>
            <ttl unit="minutes">1</ttl>
        </expiry>

        <listeners>
            <listener>
                <class>api.demo.springboot.framework.persistence.JpaCacheEventListener</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>ORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EVICTED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
                <events-to-fire-on>REMOVED</events-to-fire-on>
                <events-to-fire-on>UPDATED</events-to-fire-on>
            </listener>
        </listeners>

        <resources>
            <heap unit="entries">1000</heap>
        </resources>
    </cache-template>

    <cache
        alias="default-update-timestamps-region"
    >
        <expiry>
            <ttl unit="minutes">1</ttl>
        </expiry>

        <listeners>
            <listener>
                <class>api.demo.springboot.framework.persistence.JpaCacheEventListener</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>ORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EVICTED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
                <events-to-fire-on>REMOVED</events-to-fire-on>
                <events-to-fire-on>UPDATED</events-to-fire-on>
            </listener>
        </listeners>

        <resources>
            <heap unit="entries">1000</heap>
        </resources>
    </cache>

    <cache
        alias="default-query-results-region"
        uses-template="queriesDefault"
    />

    <cache
        alias="DemoEntity"
        uses-template="entitiesDefault"
    >
        <value-type>api.demo.springboot.application.persistence.entities.DemoEntity</value-type>
    </cache>
</config>

DemoEntity.java

import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springframework.cache.annotation.Cacheable;

import java.io.Serializable;

@Data

@Entity
@Table(name = "DEMO_TABLE")

@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class DemoEntity implements Serializable {
    private static final long serialVersionUID = 0L;

    @Id
    @Column(name = "ID")
    private Integer id;

    @Column(name = "DESCRIPTION")
    private String description;

}

JpaCacheEventListener.java

import lombok.extern.slf4j.Slf4j;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class JpaCacheEventListener implements CacheEventListener<Object, Object> {

    @Override
    public void onEvent(CacheEvent event) {
        if (log.isTraceEnabled()) {
            log.trace(
                "{ type: {}, key: {} }",
                event.getType(),
                event.getKey()
            );
        }
    }

}

Solution

  • I finally found a solution to all the issues.

    I report ONLY the fixes to the posted source code:

    application.yaml

    spring:
      jpa:
        properties:
          hibernate:
            cache:
              region.factory_class: org.hibernate.cache.jcache.internal.JCacheRegionFactory
              use_second_level_cache: true
              use_query_cache: true
    
            javax:
              cache:
                provider: org.ehcache.jsr107.EhcacheCachingProvider
                uri: file:///app/config/ehcache.xml
              missing_cache_strategy: fail
    

    ehcache.xml

        <!-- Entities (Mandatory: alias must be a FQDN of the entity in order to properly instantiate the cache as configured here!)-->
    
        <cache
            alias="it.sisal.betting.api.demo.springboot.application.persistence.entities.DemoEntity"
            uses-template="entitiesDefault"
        />
    

    No everything works!!!