Backend Development 10 min read

Master Spring Cache with JSR‑107 and Ehcache: A Step‑by‑Step Guide

This tutorial explains how to enable and use Spring Cache with annotations like @Cacheable, @CacheEvict, and @CachePut, integrates JSR‑107 and Ehcache, provides full Maven and configuration examples, and demonstrates caching behavior through a series of practical code snippets and tests.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Cache with JSR‑107 and Ehcache: A Step‑by‑Step Guide

Environment: springboot 2.6.12 + JSR107 + Ehcache + JPA

Spring Cache is a framework that implements annotation‑based caching. It uses the CacheManager interface to unify different cache technologies, simplifying cache operations for business logic. Annotations such as @Cacheable , @CacheEvict and @CachePut enable easy creation, update, and removal of cached data.

Why use Spring Cache?

Simplify operations : Annotations greatly reduce boilerplate cache handling code, letting developers focus on business logic.

Improve performance : Caching frequently accessed data or computation results reduces database and external calls, speeding up responses.

Extensibility : Spring Cache abstracts the underlying provider, allowing seamless switching between Redis, EhCache, or other solutions.

Safety : It provides cache consistency guarantees, preventing data inconsistency in distributed environments.

Overall, Spring Cache improves application performance and maintainability, making the system more robust and flexible.

Since Spring 3.1, the framework offers transparent cache support similar to transaction management. From Spring 4.1 onward, the cache abstraction supports JSR‑107 annotations and additional customization options.

1. Basic Usage

Spring provides the following cache annotations:

@Cacheable : Triggers caching of method results.

@CacheEvict : Triggers cache eviction.

@CachePut : Updates the cache without affecting method execution.

@Caching : Combines multiple cache operations on a single method.

@CacheConfig : Shares common cache configuration at the class level.

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Typical service method annotation:

<code>@Service
public class StorageService {
    @Resource
    private StorageRepository sr;
    // Custom key generator bean name
    @Cacheable(value = {"cache_storage"}, keyGenerator = "storageKey")
    public Storage getStorage(Long id) {
        return sr.findById(id).get();
    }
}</code>

Custom key generator implementation:

<code>@Component("storageKey")
public class StorageKeyGenerator implements KeyGenerator {
    private static final String KEY_PREFIX = "storage_";
    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder sb = new StringBuilder();
        for (Object param : params) {
            sb.append(param);
        }
        return KEY_PREFIX + sb.toString();
    }
}</code>

Controller to test the cache:

<code>@RestController
@RequestMapping("/storages")
public class StorageController {
    @Resource
    private StorageService storageService;
    @GetMapping("/{id}")
    public Object get(@PathVariable("id") Long id) {
        return storageService.getStorage(id);
    }
}</code>

First request logs the SQL query; subsequent requests hit the cache and no SQL is printed, confirming cache effectiveness.

JSR‑107 annotations can be used with SpEL expressions.

2. Combining JSR‑107 and EhCache

Spring‑JSR107 annotation mapping:

Add the following dependencies to pom.xml :

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;mysql&lt;/groupId&gt;
  &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.ehcache&lt;/groupId&gt;
  &lt;artifactId&gt;ehcache&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;javax.cache&lt;/groupId&gt;
  &lt;artifactId&gt;cache-api&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Service class using JCache annotations:

<code>@Service
public class StorageService {
    @Resource
    private StorageRepository sr;
    @Transactional
    @CachePut(cacheName = "cache_storage", cacheKeyGenerator = JCacheKeyGenerator.class)
    public Storage save(@CacheValue Storage storage) {
        return sr.saveAndFlush(storage);
    }
    @CacheResult(cacheName = "cache_storage", cacheKeyGenerator = JCacheKeyGenerator.class)
    public Storage getStorage(Long id) {
        return sr.findById(id).get();
    }
    @Transactional
    @CacheRemove(cacheName = "cache_storage", cacheKeyGenerator = JCacheKeyGenerator.class)
    public void removeStorage(Long id) {
        sr.deleteById(id);
    }
    @Transactional
    @CachePut(cacheName = "cache_storage", cacheKeyGenerator = JCacheKeyGenerator.class)
    public Storage updateStorage(@CacheValue Storage storage) {
        return sr.saveAndFlush(storage);
    }
}
// Note: All cacheKeyGenerator references must use the same class.</code>

Custom JCache key generator:

<code>public class JCacheKeyGenerator implements CacheKeyGenerator {
    private static final String KEY_PREFIX = "storage_";
    @Override
    public GeneratedCacheKey generateCacheKey(CacheKeyInvocationContext<? extends Annotation> ctx) {
        CacheInvocationParameter[] params = ctx.getAllParameters();
        StringBuilder sb = new StringBuilder();
        for (CacheInvocationParameter param : params) {
            if (param.getValue() instanceof Storage) {
                Storage s = (Storage) param.getValue();
                sb.append(s.getId());
            } else {
                sb.append((Long) param.getValue());
            }
        }
        return new StorageGeneratedCacheKey(KEY_PREFIX + sb.toString());
    }
}</code>

Configuration in application.yml :

<code>spring:
  cache:
    cacheNames:
      - cache_storage
    ehcache:
      config: classpath:ehcache.xml</code>

Ehcache XML configuration:

<code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false"&gt;
  &lt;diskStore path="java.io.tmpdir/Tmp_EhCache"/&gt;
  &lt;defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/&gt;
  &lt;cache name="cache_storage" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/&gt;
&lt;/ehcache&gt;</code>

Testing steps:

Add a new record (ID = 4) – the service’s @CachePut stores it in the cache.

Query the record – no SQL is logged, confirming the cache hit.

Delete the record – cache is evicted, and a subsequent query triggers a SQL query.

backend developmentcachingSpring BootSpring CacheEhcacheJSR-107
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.