Sitemap
Oracle Developers

Aggregation of articles from Oracle engineers, Oracle ACEs, and Java Champions on all things Oracle technology. The views expressed are those of the authors and not necessarily of Oracle.

Caching Data with Spring Cache, Spring Data JPA, Jakarta Persistence, and the Oracle AI Database 26ai

13 min readJan 6, 2026

--

Press enter or click to view image in full size
Photo by Alina Grubnyak on Unsplash

by Juarez Junior

Introduction

This blog post utilizes a Spring Boot 4 application to demonstrate the available Spring Cache abstractions, leveraging an Oracle Database. It focuses on the four main cache-related annotations provided by Spring — @EnableCaching, @Cacheable, @CacheEvict, @Caching, and @CacheConfig.

The runtime behavior closely follows the official Spring caching reference guide, specifically, the Spring Cache annotations reference.

A simple data persistence scenario presents how to configure the annotations, and what happens when reading, updating, and evicting data through a BookRepository.java interface and a simple command-line AppRunner instance.

So, without further ado, let’s get started!

Prerequisites

Step 1: Configure app dependencies

Configure your Maven pom.xml file with the following dependencies:

Using Maven

    <!-- Oracle AI Database 26ai JDBC / UCP -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc17</artifactId>
<version>${jdbc.version}</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp17</artifactId>
<version>${jdbc.version}</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

Step 2: Configure Spring Boot properties

Configure your application.properties file as shown below.

Beyond the Oracle JDBC driver, we’ve replaced HikariCP (the default connection pool in Spring Boot) with the Oracle UCP (Universal Connection Pool).

I’ve written an interesting blog post on why you should consider it. If you’re interested, please check it out — A Guide to Migrating from HikariCP to Oracle UCP (Universal Connection Pool).

Note that I’ve also enabled Hibernate query logging to allow you to follow the underlying HQL queries paired with different method calls. Feel free to disable them if you prefer that.

spring.application.name=spring-cache-jpa-oracle-db
spring.threads.virtual.enabled=true

# JDBC connection to Oracle AI Database 26ai
# spring.datasource.url=jdbc:oracle:thin@[hostname]:[port]/[DB service/name
# spring.datasource.username=<YOUR_DB_USER_ID>
# spring.datasource.password=<YOUR_DB_USER_PW>

spring.datasource.url=jdbc:oracle:thin:@localhost:1521/FREEPDB1
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}

# Properties for UCP - Universal Connection Pool (UCP). Spring Boot 2.4.0 or above is required
spring.datasource.type=oracle.ucp.jdbc.PoolDataSource
spring.datasource.oracleucp.connection-pool-name=connectionPoolName1
spring.datasource.oracleucp.initial-pool-size=1
spring.datasource.oracleucp.min-pool-size=1
spring.datasource.oracleucp.max-pool-size=2
spring.datasource.oracleucp.connection-factory-class-name=oracle.jdbc.pool.OracleDataSource

# Enable JPA / Hibernate debugging
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.transaction.jta.platform=org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform

logging.level.org.hibernate.SQL=INFO
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=INFO

Step 3: Enable Spring Cache

Caching in Spring is opt-in, so you can enable it centrally using the @EnableCaching annotation.

@EnableCaching activates Spring’s cache interception infrastructure, so once enabled, Spring scans for cache annotations such as @Cacheable and@CacheEvict and automatically applies proxy-based behavior around the annotated methods. No cache-specific code is required inside the business logic itself!

package com.oracle.dev.jdbc.spring.cache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CachingApplication {

public static void main(String[] args) {
SpringApplication.run(CachingApplication.class, args);
}

}

Step 4: Centralizing Cache Configuration

The core of the caching behavior lives in the BookRepository interface:

package com.oracle.dev.jdbc.spring.cache;

import java.util.List;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

@CacheConfig(cacheNames = "books")
public interface BookRepository extends JpaRepository<Book, Long> {
...
}

@CacheConfig(cacheNames = "books") defines a default cache name for all cache operations in this repository, so all caching operations use the same cache region by default. This declarative approach improves readability and makes it easier to change the cache configuration later if needed.

Step 5: Getting to know the basics of Spring Cache annotations

@Cacheable

The simplest caching example is a lookup by ISBN:

  @Cacheable(key = "#isbn")
Book getByIsbn(String isbn);

Here’s what happens at runtime:

  1. On the first invocation with a given ISBN, Spring executes the corresponding repository method.
  2. JPA queries the Oracle Database instance and returns a relatedBook instance.
  3. Spring stores the result in the "books" cache using a key derived from the isbn parameter.
  4. On subsequent invocations with the same ISBN, Spring bypasses the database query entirely and returns the cached value instead.

This behavior is demonstrated clearly by theAppRunnerwhere getByIsbn("7369") will be called twice in a row. The logs prove that the first call hits the database, while the second call is served from the cache.

@CacheEvict

Caching is only useful if stale data is removed when underlying data changes. This is where @CacheEvict comes into play:

  @Transactional
@CacheEvict(key = "#isbn")
void deleteByIsbn(String isbn);

This method illustrates two important points:

  • Because a JPA delete operation requires an active transaction, the method is annotated with the@Transactional annotation.
  • @CacheEvict removes the cache entry associated with the given ISBN when the delete operation is executed, ensuring cache consistency.

After deleteByIsbn("7369") is called, the corresponding cache entry is removed. A subsequent call to the Java methodgetByIsbn("7369") results in a cache miss and forces a fresh database lookup, ensuring that stale data is not returned.

@Caching

More complex scenarios often require multiple cache actions in a single method call. Spring provides the @Caching annotation for exactly this purpose:

  @Caching(
cacheable = {
@Cacheable(key = "#isbn")
},
evict = {
@CacheEvict(key = "'all'")
})
@Query("select b from Book b where b.isbn = :isbn")
Book reloadBookByIsbn(@Param("isbn") String isbn);

This method performs two cache-related actions atomically:

  • It caches the individual book under its ISBN.
  • It evicts the cached list of all books, stored under the search key 'all'.

This pattern is useful when refreshing a single entity might invalidate a broader aggregate view. In this case, reloading one book could make a previously cached “all books” list inconsistent, so that list cache entry is proactively evicted.

Step 6: Caching Collection Queries

Caching is not limited to single entities. The repository also demonstrates collection-level caching:

  @Cacheable(key = "'all'")
@Query("select b from Book b")
List<Book> findAllCached();

Here, the entire list of books is cached under a fixed key ('all'):

  • The first call loads all books from the database and caches the result.
  • Subsequent calls reuse the cached list, avoiding repeated database queries.

When the Java method reloadBookByIsbn is invoked, it evicts this 'all' entry, ensuring that the next call to the Java methodfindAllCached() recomputes and repopulates the cache with a consistent view of the data.

Step 7: Understanding the Spring Boot application components one by one

Let’s have a look at our application components and their implementations:

CachingApplication.java

As usual, this class will implement the main entry point of our Spring Boot application. The @SpringBootApplication annotation bootstraps the app by enabling component scanning, auto-configuration, and configuration support.

Speaking of caching, the @EnableCaching annotation, which enables the caching abstraction. With caching enabled, Spring can intercept method calls annotated with caching annotations and store or retrieve results from a cache, avoiding repeated database operations.

package com.oracle.dev.jdbc.spring.cache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class CachingApplication {

public static void main(String[] args) {
SpringApplication.run(CachingApplication.class, args);
}

}

Book.java

This class defines a JPA entity that represents a database table managed by Spring Data JPA. The @Entity annotation tells JPA to persist instances of this class, and the id attribute is marked as the primary key with the@Id annotation, and is automatically generated using the@GeneratedValueannotation.

The class models a simple domain object with three properties—id, isbn, and title—and includes a no-argument constructor as well as a full constructor, along with standard getters and setters.

package com.oracle.dev.jdbc.spring.cache;

import java.util.Objects;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class Book {

private @Id @GeneratedValue Long id;
private String isbn;
private String title;

public Book() {
}

public Book(Long id, String isbn, String title) {
this.id = id;
this.isbn = isbn;
this.title = title;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getIsbn() {
return isbn;
}

public void setIsbn(String isbn) {
this.isbn = isbn;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

@Override
public int hashCode() {
return Objects.hash(isbn, id, title);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Book other = (Book) obj;
return Objects.equals(isbn, other.isbn) && Objects.equals(id, other.id) && Objects.equals(title, other.title);
}

public String toString() {
return String.format("%20s %20s %20s", id, isbn, title);

}

}

BookRepository.java

The BookRepository interface is annotated with @CacheaConfig(cacheNames="books") , which defines a default cache name for all cache operations in this repository.

This class-level configuration centralizes cache-related metadata, making the code easier to maintain and ensuring that all methods in this repository use the same underlying cache region ("books") by default unless they explicitly override it.

The Java method @Cacheable(key="#isbn") Book getByIsbn(String isbn) demonstrates how @Cacheable works for a single entity lookup.

As explained before, on the very first invocation for a given ISBN, Spring executes the repository method, hits the database through JPA, and stores the resulting Book instance in the "books" cache under a key derived from the isbn parameter.

On subsequent calls with the same ISBN, the database query is bypassed, and the value is returned directly from the cache, which is exactly what the AppRunner logs illustrate when it calls getByIsbn("7369") twice in a row.

The Java method @Transactional @CacheEvict(key="#isbn") void deleteByIsbn(String isbn) illustrates how the@CacheEvict annotation removes a single cache entry when the underlying data changes. Because a JPA delete operation requires an active transaction, the repository method is annotated with @Transactionalso that Spring opens a transactional EntityManager for the duration of the call.

Demarcated by the transaction, Spring Data JPA executes the delete operation, and Spring Cache evicts the "books" cache entry whose key matches the isbn argument. This guarantees that the cache does not return stale data and that the delete is performed safely within a transaction.

After the eviction operation completes, the next call to getByIsbn for that same ISBN becomes a cache miss and forces a fresh lookup from the database, repopulating the cache with the most recent state, or nullIf the record is truly deleted.

The Java method Book reloadBookByIsbn(String isbn)annotated with @Caching(cacheable={@Cacheable(key="#isbn")},evict={@CacheEvict(key="all")})and backed by the query @Query("select b from Book b where b.isbn = :isbn"), showcases how Caching can group multiple cache operations on a single method invocation.

In this case, calling reloadBookByIsbn performs a targeted database query for the given ISBN and caches that single Book occurrence under the key derived from #isbn, while simultaneously evicting the cached list of all books stored under the synthetic key 'all'.

This pattern is useful when an operation both refreshes a specific item and invalidates a broader aggregate cache that may now be inconsistent, such as a cached “all books” list.

The Java methodCacheable(key="all") List<Book> findAllCached(), using @Query("select b from Book b"), demonstrates collection-level caching with theCacheableannotation. The first invocation loads the complete list of Book entities from the database and stores them in the "books" cache under the fixed key 'all'.

Subsequent calls reuse the cached list rather than re-querying the database, which can significantly improve performance for frequently requested, rarely changing aggregates. When thereloadBookByIsbn method executes, it evicts this 'all' entry so that the list will be recomputed and repopulated the next time findAllCached() is called, ensuring that clients always see a consistent view.

package com.oracle.dev.jdbc.spring.cache;

import java.util.List;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

@CacheConfig(cacheNames = "books")
public interface BookRepository extends JpaRepository<Book, Long> {

@Cacheable(key = "#isbn")
Book getByIsbn(String isbn);

@Transactional
@CacheEvict(key = "#isbn")
void deleteByIsbn(String isbn);

@Caching(cacheable = { @Cacheable(key = "#isbn") }, evict = { @CacheEvict(key = "'all'") })
@Query("select b from Book b where b.isbn = :isbn")
Book reloadBookByIsbn(@Param("isbn") String isbn);

@Cacheable(key = "'all'")
@Query("select b from Book b")
List<Book> findAllCached();

}

AppRunner.java

An AppRunner component is used to run custom logic once, automatically, right after the Spring Boot application starts. By implementing theCommandLineRunner interface and being annotated with the @Component annotation, Spring detects it as a bean and invokes its run(...) method after the application context is fully initialized. It exercises all of the cache behaviors as soon as the application starts.

First, it inserts two Book records (ISBNs "7369" and "7499") into the database and logs their creation.

It then calls getByIsbn("7369") twice in succession, where the first call triggers a database hit and populates the "books" cache for that ISBN, while the second call transparently retrieves the same Book from the cache, which you can see in the log messages describing the first call versus second call for the same key.

Next, it calls the Java methodfindAllCached() twice, showing that the first call caches the complete list under the 'all' key and the second call serves the list entirely from the cache.

To demonstrate how the@CacheEvict annotation works,AppRunner invokes the Java methoddeleteByIsbn("7369"), removing both the database row and the specific cache entry for that ISBN. Subsequent call to getByIsbn("7369") becomes a cache miss that requires a new database lookup (and repopulation).

Finally, the Java methodreloadBookByIsbn("7499") is called to show the @Caching annotation in action. This single method call refreshes the cache entry for ISBN "7499" and evicts the cached 'all' list, after which a new call to findAllCached() forces the “all books” cache to be recomputed and repopulated, demonstrating how combined cache operations help maintain consistency between individual entries and aggregate views.

package com.oracle.dev.jdbc.spring.cache;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements CommandLineRunner {

private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);

private final BookRepository bookRepository;

public AppRunner(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}

@Override
public void run(String... args) throws Exception {

logger.info("Adding books...");
Book first = new Book();
first.setIsbn("7369");
first.setTitle("BOOK1");
logger.info("ADDING BOOK ISBN 7369 --> " + bookRepository.save(first));

Book second = new Book();
second.setIsbn("7499");
second.setTitle("BOOK2");
logger.info("ADDING BOOK ISBN 7499 --> " + bookRepository.save(second));

logger.info("Retrieving books using @Cacheable (first call hits DB, second call uses cache)...");
logger.info("FIRST CALL - BOOK ISBN 7369 --> " + bookRepository.getByIsbn("7369"));
logger.info("SECOND CALL - BOOK ISBN 7369 --> " + bookRepository.getByIsbn("7369"));

logger.info("Retrieving all books using @Cacheable on collection (first call caches, second reuses)...");
logger.info("FIRST CALL - ALL BOOKS --> " + bookRepository.findAllCached());
logger.info("SECOND CALL - ALL BOOKS --> " + bookRepository.findAllCached());

logger.info("Evicting a single book from cache using @CacheEvict (deleteByIsbn)...");

bookRepository.deleteByIsbn("7369");

logger.info("After eviction, retrieving ISBN 7369 again (cache miss, DB lookup)...");
logger.info("BOOK ISBN 7369 AFTER EVICT --> " + bookRepository.getByIsbn("7369"));

logger.info("Using @Caching to combine @Cacheable and @CacheEvict on reloadBookByIsbn...");
logger.info("RELOAD BOOK ISBN 7499 --> " + bookRepository.reloadBookByIsbn("7499"));
logger.info("After @Caching, retrieving all books again (list cache was evicted)...");
logger.info("ALL BOOKS AFTER @Caching --> " + bookRepository.findAllCached());

}

}

Step 7: Run the Spring Cache app

To execute the app, run the command below.

mvn clean package
mvn spring-boot:run

The log output clearly illustrates when the database is accessed versus when cached values are returned, making it easy to reason about the runtime behavior of each annotation.

2026-01-06T14:50:45.406Z  INFO 36448 --- [spring-cache-jpa-oracle-db] [           main] org.hibernate.orm.connections.pooling    : HHH10001005: Database info:
Database JDBC URL [jdbc:oracle:thin:@localhost:1521/FREEPDB1]
Database driver: Oracle JDBC driver
Database dialect: OracleDialect
Database version: 23.26
Default catalog/schema: undefined/VECTOR_USER
Autocommit mode: undefined/unknown
Isolation level: READ_COMMITTED [default READ_COMMITTED]
JDBC fetch size: 10
Pool: DatasourceConnectionProviderImpl
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
Hibernate:
create global temporary table HTE_book(rn_ number(10, 0) not null, id number(19, 0), isbn varchar2(255 char), title varchar2(255 char), primary key (rn_))
on commit delete rows
Hibernate:
drop table if exists book cascade constraints
Hibernate:
drop sequence if exists book_seq
Hibernate:
create sequence book_seq start with 1 increment by 50
Hibernate:
create table book (
id number(19,0) not null,
isbn varchar2(255 char),
title varchar2(255 char),
primary key (id)
)
2026-01-06T14:50:46.291Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2026-01-06T14:50:46.355Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] o.s.d.j.r.query.QueryEnhancerFactories : Hibernate is in classpath; If applicable, HQL parser will be used.
2026-01-06T14:50:46.872Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.d.j.spring.cache.CachingApplication : Started CachingApplication in 3.589 seconds (process running for 3.913)
2026-01-06T14:50:46.875Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : Adding books...
Hibernate:
select
book_seq.nextval
Hibernate:
insert
into
book
(isbn, title, id)
values
(?, ?, ?)
2026-01-06T14:50:46.976Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : ADDING BOOK ISBN 7369 --> 1 7369 BOOK1
Hibernate:
select
book_seq.nextval
Hibernate:
insert
into
book
(isbn, title, id)
values
(?, ?, ?)
2026-01-06T14:50:46.984Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : ADDING BOOK ISBN 7499 --> 2 7499 BOOK2
2026-01-06T14:50:46.984Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : Retrieving books using @Cacheable (first call hits DB, second call uses cache)...
Hibernate:
select
b1_0.id,
b1_0.isbn,
b1_0.title
from
book b1_0
where
b1_0.isbn=?
2026-01-06T14:50:47.136Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : FIRST CALL - BOOK ISBN 7369 --> 1 7369 BOOK1
2026-01-06T14:50:47.136Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : SECOND CALL - BOOK ISBN 7369 --> 1 7369 BOOK1
2026-01-06T14:50:47.137Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : Retrieving all books using @Cacheable on collection (first call caches, second reuses)...
Hibernate:
select
b1_0.id,
b1_0.isbn,
b1_0.title
from
book b1_0
2026-01-06T14:50:47.149Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : FIRST CALL - ALL BOOKS --> [ 1 7369 BOOK1, 2 7499 BOOK2]
2026-01-06T14:50:47.149Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : SECOND CALL - ALL BOOKS --> [ 1 7369 BOOK1, 2 7499 BOOK2]
2026-01-06T14:50:47.149Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : Evicting a single book from cache using @CacheEvict (deleteByIsbn)...
Hibernate:
select
b1_0.id,
b1_0.isbn,
b1_0.title
from
book b1_0
where
b1_0.isbn=?
Hibernate:
delete
from
book
where
id=?
2026-01-06T14:50:47.164Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : After eviction, retrieving ISBN 7369 again (cache miss, DB lookup)...
Hibernate:
select
b1_0.id,
b1_0.isbn,
b1_0.title
from
book b1_0
where
b1_0.isbn=?
2026-01-06T14:50:47.167Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : BOOK ISBN 7369 AFTER EVICT --> null
2026-01-06T14:50:47.167Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : Using @Caching to combine @Cacheable and @CacheEvict on reloadBookByIsbn...
Hibernate:
select
b1_0.id,
b1_0.isbn,
b1_0.title
from
book b1_0
where
b1_0.isbn=?
2026-01-06T14:50:47.174Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : RELOAD BOOK ISBN 7499 --> 2 7499 BOOK2
2026-01-06T14:50:47.174Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : After @Caching, retrieving all books again (list cache was evicted)...
Hibernate:
select
b1_0.id,
b1_0.isbn,
b1_0.title
from
book b1_0
2026-01-06T14:50:47.177Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ main] c.o.dev.jdbc.spring.cache.AppRunner : ALL BOOKS AFTER @Caching --> [ 2 7499 BOOK2]
2026-01-06T14:50:47.181Z INFO 36448 --- [spring-cache-jpa-oracle-db] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
------------------------------------------------------------------------

You can also get a Maven project with a complete code sample on GitHub.

Wrapping it up

That’s it! Spring Cache provides a powerful yet lightweight abstraction for improving performance in data-intensive applications. By combining a few well-chosen annotations, you can dramatically reduce database load while keeping your data consistent and your codebase clean.

For software engineers building Spring applications on Oracle AI Database 26ai, this pattern offers a clear, maintainable way to introduce caching without sacrificing correctness or readability.

By understanding how these annotations interact, you can confidently apply Spring Cache to real-world workloads and scale your applications more efficiently!

I hope you enjoyed this blog post, so stay tuned!

References

Oracle Developers and Oracle OCI Free Tier

Join our Oracle Developers channel on Slack to discuss Java, GenAI, Agentic AI, JDK, JDBC, GraalVM, Micronaut, Spring Boot, Helidon, Quarkus, Reactive Streams, Cloud, DevOps, SRE, IaC, and other topics!

Build, test, and deploy your applications on Oracle Cloud — for free! Get access to OCI Cloud Free Tier!

--

--

Juarez Junior
Juarez Junior

Written by Juarez Junior

Senior Principal Developer Evangelist @ Oracle ☕️🐍🥑 https://linktr.ee/juarezjunior

Oracle Developers
Oracle Developers

Published in Oracle Developers

Aggregation of articles from Oracle engineers, Oracle ACEs, and Java Champions on all things Oracle technology. The views expressed are those of the authors and not necessarily of Oracle.