Caching & Beyond JPA
Spring's caching abstraction with @Cacheable, plus when to reach for JdbcClient, MongoDB, or Redis.
On this page
JPA is the default for relational data, but the Spring Data family is bigger, and one cross-cutting concern - caching - can speed up any of it. This lesson rounds out the Data module with the tools you'll reach for beyond plain JPA.
Caching with @Cacheable
Some reads are expensive and repeated with the same inputs - a rarely-changing lookup, an aggregation. Spring's caching abstraction stores the result so repeat calls skip the work entirely:
@Service
class CatalogStats {
@Cacheable("bookCountByAuthor")
public long countByAuthor(String author) { // runs once per author…
return bookRepository.countByAuthor(author); // …then served from cache
}
@CacheEvict(value = "bookCountByAuthor", allEntries = true)
public void onCatalogChanged() { } // clear the cache when data changes
}Enable it with @EnableCaching on a config class. @Cacheable caches by method
arguments; @CacheEvict clears entries when the underlying data changes.
If someone asks you a hard sum and then asks the exact same sum a minute later, you
don't recompute it - you remember. @Cacheable gives your method that memory: the first
call does the work, and identical later calls return the remembered answer instantly.
@CacheEvict is you forgetting the answer when the numbers change, so you don't repeat a
stale one.
Cache invalidation is the hard part
A cache that returns stale data is worse than no cache. Evict or update entries whenever the source changes, keep cached data reasonably short-lived, and only cache reads that are genuinely expensive and repeated - not everything.
Beyond JPA
JPA/Hibernate isn't always the right tool. The Spring Data ecosystem covers more:
| Need | Reach for |
|---|---|
| Full ORM over relational data | Spring Data JPA (the default) |
| Simple, explicit SQL without an ORM | JdbcClient (fluent JDBC in Spring 6.1+) |
| Document data | Spring Data MongoDB |
| Caching / ephemeral key-value | Spring Data Redis |
The beautiful part: they share the same repository programming model. A
MongoRepository or Spring Data Redis repository looks and feels like the
JpaRepository you already know - so the concepts from this module transfer directly.
// JdbcClient — when you want plain SQL, no entity mapping overhead
List<Book> books = jdbcClient
.sql("SELECT * FROM book WHERE copies > :min")
.param("min", 0)
.query(Book.class)
.list();For each BookVault read, decide whether @Cacheable is a good fit, and if so what would invalidate the cache:
- A homepage stat: "total books in the catalog," changing at most a few times a day.
- A member's current list of active loans, which changes every time they borrow or return.
What does @Cacheable do on a method?
Key takeaways
- Spring's caching abstraction (@EnableCaching + @Cacheable) stores method results by arguments so repeated calls skip the work.
- Invalidate with @CacheEvict when the underlying data changes - stale cache data is worse than none.
- Only cache reads that are genuinely expensive and repeated, and keep cached data reasonably fresh.
- Beyond JPA: JdbcClient for plain SQL, Spring Data MongoDB for documents, Spring Data Redis for key-value/caching.
- All Spring Data modules share the same repository model, so the concepts transfer across databases.