Start Learning
Javaneer
Back to stage
Module 3·Data & Persistence

Caching & Beyond JPA

Spring's caching abstraction with @Cacheable, plus when to reach for JdbcClient, MongoDB, or Redis.

14 min readIntermediate
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.

Caching is remembering an answer you just calculated

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:

NeedReach for
Full ORM over relational dataSpring Data JPA (the default)
Simple, explicit SQL without an ORMJdbcClient (fluent JDBC in Spring 6.1+)
Document dataSpring Data MongoDB
Caching / ephemeral key-valueSpring 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();
Cache or not?

For each BookVault read, decide whether @Cacheable is a good fit, and if so what would invalidate the cache:

  1. A homepage stat: "total books in the catalog," changing at most a few times a day.
  2. 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.
Was this lesson helpful?
Edit this page on GitHub