Read-Through
On a cache miss, EntityMapLoader queries through Exposed and fills Redis and the local cache.
- Reduces repeated reads
- First read includes DB latency
Exposed + Local Cache + Redis
Caching is not only a latency optimization. It defines where a value is read, when a change reaches the database, and how each layer converges after invalidation or failure.
JdbcCacheRepository implementations separate cache reads and database persistence into Read-Through, Write-Through, Read-Only, and Write-Behind strategies. Select one from the update frequency and acceptable persistence delay of each data type.
On a cache miss, EntityMapLoader queries through Exposed and fills Redis and the local cache.
EntityMapWriter updates the cache and database in the request path.
Cache stable data, invalidate it on change, and reload it on the next read.
Accept the request, write Redis first, then let a batch persist through Exposed.
The diagram connects request handling, repository strategy, storage, and verification to show where each strategy reads data and when it persists changes to the database.
UserCacheRepository, UserCredentialsCacheRepository, and UserEventCacheRepository select Read/Write-Through, Read-Only, and Write-Behind before delegating to the shared Redisson repository and Exposed database path.
Open the full-size diagram
Near Cache consists of a local cache at L1 and Redis at L2. The Lettuce implementation uses Caffeine at L1, while the Redisson implementation uses the local cache managed by RLocalCachedMap. Exposed is not part of Near Cache; it is the database access layer invoked for cache misses and writes.
UserCacheRepository uses READ_WRITE_THROUGH_WITH_NEAR_CACHE.RLocalCachedMap; selected Lettuce suspend paths use LettuceSuspendNearCache with Caffeine.EntityMapLoader executes an Exposed SELECT on a miss.EntityMapWriter runs the Exposed transaction for Write-Through or a Write-Behind batch.deleteFromDBOnInvalidate determines whether invalidation also deletes the database row.UserCacheRepository
READ_WRITE_THROUGH_WITH_NEAR_CACHE
UserCredentialsCacheRepository
READ_ONLY_WITH_NEAR_CACHE
UserEventCacheRepository
WRITE_BEHIND_WITH_NEAR_CACHE
HTTP → JdbcCacheRepository
→ RLocalCachedMap
→ EntityMapLoader / EntityMapWriter
→ Exposed transaction → DBRedisson local cache (L1)
+ Redis RLocalCachedMap (L2)
+ AbstractJdbcRedissonRepository
+ Exposed DB loader / writerCaffeine (L1)
+ Redis via Lettuce (L2)
+ LettuceSuspendNearCache
+ AbstractSuspendedJdbcLettuceRepository
+ Exposed DB loader / writerLettuceCacheConfig
+ RedisCacheManager
+ Exposed CountryRepository
+ LettuceSuspendedCache
+ CachedCountrySuspendedRepository
+ Exposed suspended repository
+ No local L1 cacheRepeated reads in one JVM can finish in the local cache. The system still needs an explicit limit for stale local values, reconnection behavior, and writes that Redis has accepted but the database has not persisted.
Delayed Redisson invalidation messages or Lettuce RESP3 tracking notifications can leave JVM-local caches with different values.
A failure after the Redis write and before the database batch can lose a change that has not reached durable storage. Never label request acceptance as DB persistence.
Review deleteFromDBOnInvalidate before calling invalidate. Separate cache eviction from business deletion.
CacheStrategyComparisonBenchmark compares NoCache, ReadThrough, and WriteThrough against a real PostgreSQL Testcontainer through HikariCP. Values are average time in us/op; lower is better.
Open the full-size chart
| Strategy / Payload | READ_HEAVY | WRITE_HEAVY |
|---|---|---|
| NoCache / 256B | 547.7 us/op | 505.3 us/op |
| ReadThrough / 256B | 87.5 us/op | 478.6 us/op |
| WriteThrough / 256B | 50.5 us/op | 448.6 us/op |
| NoCache / 4KB | 482.6 us/op | 529.4 us/op |
| ReadThrough / 4KB | 95.9 us/op | 497.9 us/op |
| WriteThrough / 4KB | 55.9 us/op | 491.9 us/op |
Scope: READ_HEAVY uses 90% reads and 10% writes; WRITE_HEAVY uses 10% reads and 90% writes. This result does not measure Redis Near Cache, Write-Behind, production throughput, or an SLA.
Testcontainers starts Redis. Modules 01 and 02 verify Redisson-backed persistence strategies. Module 06 combines Spring Cache with Lettuce, while module 07 applies LettuceSuspendedCache to an Exposed repository. Modules 06 and 07 are Redis-only cache examples without a local L1 cache.
# Redisson + Exposed strategies
./gradlew :01-cache-strategies:test
./gradlew :01-cache-strategies:bootRun
./gradlew :02-cache-strategies-coroutines:test# Spring Cache + Lettuce + Exposed
./gradlew :06-spring-cache:test
# LettuceSuspendedCache + Exposed
./gradlew :07-spring-suspended-cache:test