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.
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><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency></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><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency></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><?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/>
<cache name="cache_storage" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/>
</ehcache></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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.