Files
gh-giuseppe-trisciuoglio-de…/skills/spring-boot-cache/references/cache-core-reference.md
2025-11-29 18:28:34 +08:00

16 KiB

Spring Boot Cache Abstraction - References

Complete API reference and external resources for Spring Boot caching.

Spring Cache Abstraction API Reference

Core Interfaces

CacheManager

Interface for managing cache instances.

public interface CacheManager {
    // Get a cache by name
    Cache getCache(String name);
    
    // Get all available cache names
    Collection<String> getCacheNames();
}

Common Implementations:

  • ConcurrentMapCacheManager - In-memory, thread-safe caching
  • SimpleCacheManager - Simple static cache configuration
  • CaffeineCacheManager - High-performance caching with Caffeine library
  • EhCacheManager - Enterprise caching with EhCache
  • RedisCacheManager - Distributed caching with Redis

Cache

Interface representing a single cache.

public interface Cache {
    // Get cache name
    String getName();
    
    // Get native cache implementation
    Object getNativeCache();
    
    // Get value by key
    ValueWrapper get(Object key);
    
    // Put value in cache
    void put(Object key, Object value);
    
    // Remove entry from cache
    void evict(Object key);
    
    // Clear entire cache
    void clear();
}

Cache Annotations

Annotation Purpose Target Parameters
@Cacheable Cache method result before execution Methods value, key, condition, unless
@CachePut Always execute, then cache result Methods value, key, condition, unless
@CacheEvict Remove entry/entries from cache Methods value, key, allEntries, condition, beforeInvocation
@Caching Combine multiple cache operations Methods cacheable, put, evict
@CacheConfig Class-level cache configuration Classes cacheNames
@EnableCaching Enable caching support Configuration classes None

Annotation Parameters

value / cacheNames

Name(s) of the cache(s) to use.

@Cacheable(value = "products")  // Single cache
@Cacheable(value = {"products", "inventory"})  // Multiple caches

key

SpEL expression to generate cache key (if not using method parameters as key).

@Cacheable(value = "products", key = "#id")
@Cacheable(value = "products", key = "#p0")  // First parameter
@Cacheable(value = "products", key = "#root.methodName + #id")
@Cacheable(value = "products", key = "T(java.util.Objects).hash(#id, #name)")

SpEL Context Variables:

  • #root.methodName - Method name being invoked
  • #root.method - Method object
  • #root.target - Target object
  • #root.targetClass - Target class
  • #root.args[0] - Method arguments array
  • #a0, #p0 - First argument
  • #result - Method result (only in @CachePut, @CacheEvict)

condition

SpEL expression evaluated before cache operation. Operation only executes if true.

@Cacheable(value = "products", condition = "#id > 0")
@Cacheable(value = "products", condition = "#price > 100 && #active == true")
@Cacheable(value = "products", condition = "#size() > 0")  // For collections

unless

SpEL expression evaluated AFTER method execution. Entry is cached only if false.

@Cacheable(value = "products", unless = "#result == null")
@CachePut(value = "products", unless = "#result.isPrivate()")

beforeInvocation

For @CacheEvict only. If true, cache is evicted BEFORE method execution (default: false).

@CacheEvict(value = "products", beforeInvocation = true)  // Evict before call
@CacheEvict(value = "products", beforeInvocation = false)  // Evict after call

allEntries

For @CacheEvict only. If true, entire cache is cleared instead of single entry.

@CacheEvict(value = "products", allEntries = true)  // Clear all entries

Configuration Reference

Maven Dependencies

<!-- Spring Cache Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>3.5.6</version>
</dependency>

<!-- Caffeine (Optional, for advanced caching) -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.6</version>
</dependency>

<!-- EhCache (Optional, for distributed caching) -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.1</version>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.8</version>
    <classifier>jakarta</classifier>
</dependency>

<!-- Redis (Optional, for distributed caching) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>3.5.6</version>
</dependency>

Gradle Dependencies

dependencies {
    // Spring Cache Starter
    implementation 'org.springframework.boot:spring-boot-starter-cache:3.5.6'

    // Caffeine
    implementation 'com.github.ben-manes.caffeine:caffeine:3.1.6'

    // EhCache
    implementation 'javax.cache:cache-api:1.1.1'
    implementation 'org.ehcache:ehcache:3.10.8'

    // Redis
    implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.5.6'
}

Application Properties (application.properties)

# General Caching Configuration
spring.cache.type=simple  # Type: simple, redis, caffeine, ehcache, jcache

# Caffeine Configuration
spring.cache.caffeine.spec=maximumSize=1000,expireAfterWrite=10m
spring.cache.cache-names=products,users,orders

# Redis Configuration
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.data.redis.password=
spring.cache.redis.time-to-live=600000  # 10 minutes in ms

# EhCache Configuration
spring.cache.jcache.config=classpath:ehcache.xml

Application Properties (application.yml)

spring:
  cache:
    type: simple
    cache-names:
      - products
      - users
      - orders
    
    caffeine:
      spec: maximumSize=1000,expireAfterWrite=10m
    
    redis:
      time-to-live: 600000  # 10 minutes in ms
    
    jcache:
      config: classpath:ehcache.xml

Performance Tuning Reference

Cache Types Comparison

Type Use Case Memory Thread-Safe Distributed
Simple Local, small data Low Yes No
Caffeine High-performance local Medium Yes No
EhCache Enterprise local High Yes Optional
Redis Distributed, large External Yes Yes

Performance Tips

1. Key Generation Strategy:

// Fast (uses method parameters directly)
@Cacheable(value = "products")  // Uses all parameters as key
@Cacheable(value = "products", key = "#id")  // Specific parameter

// Slower (computed SpEL)
@Cacheable(value = "products", key = "T(java.util.Objects).hash(#id, #name)")

2. Cache Size Tuning:

# Caffeine: Set appropriate maximumSize
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=15m

# Redis: Monitor memory usage
# MEMORY STATS command in Redis CLI

3. TTL Configuration:

# Redis: TTL in milliseconds
spring.cache.redis.time-to-live=600000  # 10 minutes

# Caffeine: In spec
spring.cache.caffeine.spec=expireAfterWrite=10m

Spring Boot Auto-Configuration

Auto-Detected Cache Managers

Spring Boot auto-configures a CacheManager based on classpath presence (in priority order):

  1. Redis - if spring-boot-starter-data-redis is present
  2. Caffeine - if caffeine library is present
  3. EhCache - if ehcache library is present
  4. Simple - default in-memory caching

To explicitly set the cache type:

spring.cache.type=redis

Conditional Bean Creation

@Bean
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("products", "users");
}

Transaction Integration

Cache + @Transactional Interaction

@Service
@Transactional
public class ProductService {
    
    @Cacheable(value = "products", key = "#id")
    @Transactional(readOnly = true)  // Combines with cache
    public Product getProduct(Long id) {
        return productRepository.findById(id).orElse(null);
    }
    
    @CachePut(value = "products", key = "#product.id")
    @Transactional  // Ensure atomicity of save + cache update
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }
    
    @CacheEvict(value = "products", key = "#id")
    @Transactional
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
}

Monitoring and Metrics

Spring Boot Actuator Integration

# Enable caching metrics
management.endpoints.web.exposure.include=metrics,health

# View cache metrics
GET http://localhost:8080/actuator/metrics
GET http://localhost:8080/actuator/metrics/cache.hits
GET http://localhost:8080/actuator/metrics/cache.misses

Custom Cache Metrics

@Component
public class CacheMetricsCollector {
    private final MeterRegistry meterRegistry;

    public void recordCacheHit(String cacheName) {
        meterRegistry.counter("cache.hits", "cache", cacheName).increment();
    }

    public void recordCacheMiss(String cacheName) {
        meterRegistry.counter("cache.misses", "cache", cacheName).increment();
    }
}

EhCache XML Configuration Reference

ehcache.xml Structure

<?xml version="1.0" encoding="UTF-8"?>
<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.0.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

    <!-- Cache Configuration -->
    <cache alias="cacheName">
        <key-type>java.lang.Long</key-type>
        <value-type>com.example.Product</value-type>
        
        <!-- Time to Live -->
        <expiry>
            <ttl unit="minutes">30</ttl>
        </expiry>
        
        <!-- Storage Configuration -->
        <resources>
            <heap unit="entries">1000</heap>
            <offheap unit="MB">50</offheap>
            <disk unit="GB">1</disk>
        </resources>
        
        <!-- Listeners (optional) -->
        <listeners>
            <listener>
                <class>com.example.CustomCacheEventListener</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
            </listener>
        </listeners>
    </cache>
</config>

Common EhCache Attributes

  • heap - On-heap memory storage (fast, limited)
  • offheap - Off-heap memory storage (slower, larger)
  • disk - Disk storage (slowest, unlimited)
  • ttl - Time to live before expiration
  • idle - Time to idle before expiration (if not accessed)

Common Pitfalls and Solutions

Problem 1: Cache Not Working

Symptoms: Cache is never hit, always querying database.

Causes & Solutions:

// Problem: @Cacheable on public method called from same bean
@Service
public class ProductService {
    @Cacheable("products")
    public Product get(Long id) { }
    
    public Product getDetails(Long id) {
        return this.get(id);  // ❌ Won't use cache (no proxy)
    }
}

// Solution: Inject service or call through interface
@Service
public class DetailsService {
    @Autowired
    private ProductService productService;
    
    public Product getDetails(Long id) {
        return productService.get(id);  // ✅ Uses cache
    }
}

// Problem: Caching non-serializable objects with Redis
@Cacheable("products")
public Product get(Long id) {
    Product p = new Product();
    p.setConnection(dbConnection);  // ❌ Not serializable
    return p;
}

// Solution: Ensure all cached objects are serializable
@Cacheable("products")
public ProductDTO get(Long id) {
    return mapper.toDTO(productRepository.findById(id));  // ✅ DTO is serializable
}

Problem 2: Stale Cache Data

Symptoms: Updates aren't reflected in cached data.

Solution:

// Always evict cache on update
@CacheEvict(value = "products", key = "#id")
public void updateProduct(Long id, UpdateRequest req) {
    Product product = productRepository.findById(id).orElseThrow();
    product.update(req);
    productRepository.save(product);
}

// Or use @CachePut to keep cache fresh
@CachePut(value = "products", key = "#result.id")
public Product updateProduct(Long id, UpdateRequest req) {
    Product product = productRepository.findById(id).orElseThrow();
    product.update(req);
    return productRepository.save(product);
}

Problem 3: Memory Leak

Symptoms: Memory usage grows unbounded.

Solution:

# Configure cache eviction policies
spring.cache.caffeine.spec=maximumSize=10000,expireAfterWrite=10m

# Redis: Set TTL
spring.cache.redis.time-to-live=600000

# Monitor cache size

External Resources

Official Documentation

Third-Party Libraries

  • spring-boot-performance-tuning - Comprehensive performance optimization
  • spring-boot-data-persistence - Database optimization patterns
  • spring-boot-rest-api-standards - API design with caching headers

Useful Articles

SpEL Reference for Cache Keys

Basic Expressions

// Method parameters
@Cacheable(key = "#id")           // Single parameter
@Cacheable(key = "#user.id")      // Object property
@Cacheable(key = "#root.args[0]") // First argument

// Composite keys
@Cacheable(key = "#id + '-' + #type")
@Cacheable(key = "T(java.util.Objects).hash(#id, #type)")

// Collections
@Cacheable(key = "#ids.toString()")
@Cacheable(condition = "#ids.size() > 0")

SpEL Context Variables

Variable Description
#root.method Method object
#root.methodName Method name
#root.target Target object
#root.targetClass Target class
#root.args Arguments array
#p<index> Argument at index
#<name> Named argument
#result Method result (@CachePut, @CacheEvict)

Testing Reference

Testing Cache Behavior

@Test
void shouldCacheResult() {
    // Arrange
    when(repository.find(1L)).thenReturn(mockObject);
    
    // Act - First call
    service.get(1L);
    
    // Assert - Database was queried
    verify(repository, times(1)).find(1L);
    
    // Act - Second call
    service.get(1L);
    
    // Assert - Database NOT queried again (cache hit)
    verify(repository, times(1)).find(1L);
}

Disabling Cache in Tests

@SpringBootTest
@PropertySource("classpath:application-test.properties")
class MyServiceTest {
    // In application-test.properties:
    // spring.cache.type=none
}