Spring Cache Quick Guide
David Kanenwisher • December 6, 2023
kotlinThe caching in Spring is managed by annotations.
- The CacheManager needs to be configured with the names of the caches.
- Need to make sure classes using the annotations are injected so the ApplicationContext can supply the configured CacheManager.
@Configuration
@EnableCaching
class CachingConfig {
@Bean
fun cacheManager(): CacheManager {
return ConcurrentMapCacheManager("skill", "skillList")
}
}
A cache manager with two caches configured: skill and skillList.
package com.example.servingwebcontent.services.skills
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
@Service
class SkillService(val skillRepository: SkillRepository) {
@Cacheable("skill")
fun findById(id: Long): Skill? {
println("findById cache miss")
return skillRepository.findByIdOrNull(id)
}
@Cacheable("skill")
fun findAll(): Iterable<Skill> = skillRepository.findAll()
@CacheEvict("skill", allEntries=true)
fun saveSkill(skill: Skill): Skill = skillRepository.save(skill)
}
The SkillService stores the results of the queries findById and findAll in the cache and evicts all the entries whenever saveSkill is called.
@Cacheable("skill", key = "#id", unless = "#result == null")
fun findById(id: Long): Skill? {
println("findById cache miss")
return skillRepository.findByIdOrNull(id)
}
Cache with the id passed unless the result is null.
package com.example.servingwebcontent.services.skills
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.CachePut
import org.springframework.cache.annotation.Cacheable
import org.springframework.cache.annotation.Caching
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
@Service
class SkillService(val skillRepository: SkillRepository) {
@Cacheable("skill", key = "#id", unless = "#result == null")
fun findById(id: Long): Skill? {
return skillRepository.findByIdOrNull(id)
}
@Cacheable("skillList", key = "#userId", unless = "#result.empty")
fun findAll(userId: Long): Iterable<Skill> {
return skillRepository.findAll()
}
@Caching(
evict = [
CacheEvict("skillList", allEntries = true)
],
put = [
CachePut("skill", key = "#result.id")
]
)
fun saveSkill(skill: Skill): Skill = skillRepository.save(skill)
}
- Uses a
skilland askillListcache findByIduses theskillcache based on theidpassed in and stores the value unless it’s null.findAlluses theskillListcache based on the onuserIdpassed in and stores the value unless it’s empty(expecting a list).saveSkilluses@Cachingto operate on both caches. It evicts all entries inskillListand updates the entry for the saved skill based on the id in the result.
Troubleshooting
Cache logging is super helpful when things aren’t getting cached how you expect. Remember to read the whole line because it is very verbose. I misunderstood it for a while because I assumed I knew which cache it was working on.
Log Spring Cache output
logging.level.org.springframework.cache=TRACE
Injecting the CacheManager to see what’s actually stored is a good way to see what the keys look like and how the cache is changing over time:
@Autowired
private lateinit var cacheManager: CacheManager