Spring data redis
Skill rrezartprebreza/spring-boot-skills/skills/spring-boot-3/spring-data-redis
Use when implementing caching, session storage, rate limiting, or any Redis integration. Covers cache-aside pattern, key naming, TTL strategy, and serialization config.From its SKILL.md
npx -y skills add rrezartprebreza/spring-boot-skills --skill spring-data-redisAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- reads credentialsReads from 3 credential sources: `REDIS_HOST` and 2 more.
SKILL.md
6.2 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Spring Data Redis
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Configuration
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // JSON, not Java serialize
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withCacheConfiguration("orders", config.entryTtl(Duration.ofMinutes(5)))
.withCacheConfiguration("products", config.entryTtl(Duration.ofHours(1)))
.build();
}
}
Key Naming Convention
{app}:{domain}:{id} → orders:order:uuid-here
{app}:{domain}:list:{filter} → orders:order:list:status:PENDING
{app}:session:{userId} → orders:session:uuid-here
{app}:ratelimit:{ip} → orders:ratelimit:192.168.1.1
@Cacheable — Declarative Caching
@Service
@RequiredArgsConstructor
public class ProductService {
@Cacheable(value = "products", key = "#id")
public ProductResponse findById(UUID id) {
return productRepository.findById(id)
.map(ProductResponse::from)
.orElseThrow(() -> new EntityNotFoundException("Product not found: " + id));
}
@CachePut(value = "products", key = "#result.id") // update cache after write
@Transactional
public ProductResponse update(UUID id, UpdateProductRequest request) {
Product product = productRepository.findById(id).orElseThrow();
product.update(request);
return ProductResponse.from(productRepository.save(product));
}
@CacheEvict(value = "products", key = "#id") // invalidate on delete
@Transactional
public void delete(UUID id) {
productRepository.deleteById(id);
}
@CacheEvict(value = "products", allEntries = true) // clear all
public void clearCache() {}
}
Manual Cache-Aside Pattern
@Service
@RequiredArgsConstructor
public class OrderCacheService {
private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper;
private static final Duration TTL = Duration.ofMinutes(5);
public Optional<OrderResponse> get(UUID orderId) {
String key = "orders:order:" + orderId;
Object cached = redisTemplate.opsForValue().get(key);
if (cached == null) return Optional.empty();
return Optional.of(objectMapper.convertValue(cached, OrderResponse.class));
}
public void put(OrderResponse order) {
String key = "orders:order:" + order.id();
redisTemplate.opsForValue().set(key, order, TTL);
}
public void evict(UUID orderId) {
redisTemplate.delete("orders:order:" + orderId);
}
}
Rate Limiting with Redis
@Component
@RequiredArgsConstructor
public class RateLimiter {
private final RedisTemplate<String, String> redisTemplate;
public boolean isAllowed(String identifier, int maxRequests, Duration window) {
String key = "ratelimit:" + identifier;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, window);
}
return count <= maxRequests;
}
}
application.yml
spring:
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
timeout: 2000ms
lettuce:
pool:
max-active: 10
max-idle: 5
min-idle: 2
cache:
type: redis
Cache Stampede
When a hot key expires, every concurrent request misses at once and they all hammer the DB to recompute the same value (the "thundering herd"). For expensive, high-traffic loads, let one caller compute while the rest wait:
// sync = true — only one thread computes the value; others block on it
@Cacheable(value = "products", key = "#id", sync = true)
public ProductResponse findById(UUID id) { ... }
sync = true serializes recomputation per key within a single instance. For a fleet-wide guarantee,
add a short Redis lock (SETNX with a TTL) around the recompute. Pair with jittered TTLs so a batch of
keys written together doesn't all expire on the same second.
Gotchas
- Agent uses Java serialization for values — always use JSON (
GenericJackson2JsonRedisSerializer) - Agent caches entities with JPA lazy fields — cache DTOs/response objects, not entities
- Agent uses no TTL — always set expiry, memory is not infinite
- Agent forgets
@EnableCaching—@Cacheablesilently does nothing without it - Agent caches
nullvalues — use.disableCachingNullValues()to avoid storing misses - Agent leaves hot keys unprotected — use
@Cacheable(sync = true)to prevent stampede on expiry - Agent gives every entry the same TTL — add jitter so keys don't expire in a synchronized wave
What ships with it: 3 files
5.4 KB alongside SKILL.md
examples/
- bad-cache-service.java1.3 KB
- good-cache-service.java1.1 KB
templates/
- RedisConfig.java2.9 KB
Gives 0 of the 12 instructions most databases sql skills give in ~1.3k tokens
Counted across 609 of the 712 authors here whose files we hold, read 2026-09-06
- Index all foreign key columnsin 26 of 609
- Use cursor pagination instead of offsetin 25 of 609, across 20 files
- Use timestamptz for timestampsin 21 of 609
- Specify columns instead of using select starin 20 of 609, across 10 files
- Use parameterized queries for all database interactionsin 20 of 609, across 19 files
- Use Enum for categorical datain 17 of 609, across 7 files
- Order by frequently filtered columnsin 17 of 609, across 7 files
- Batch data insertsin 17 of 609, across 7 files
- Use expand-contract pattern for schema changesin 17 of 609
- Use materialized views for real-time aggregationsin 16 of 609, across 6 files
- Partition tables by timein 16 of 609, across 6 files
- Use smallest appropriate data typesin 16 of 609, across 6 files
Said here and by no other author read
- Use GenericJackson2JsonRedisSerializer for all values
- Cache DTOs instead of JPA entities
- Enable caching with @EnableCaching
- Disable caching null values
- Use sync equals true to prevent cache stampede
- Add jitter to TTLs for high traffic keys
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.