diff --git a/spring-caching/pom.xml b/spring-caching/pom.xml
index 80644f8a5f..b13755dafd 100644
--- a/spring-caching/pom.xml
+++ b/spring-caching/pom.xml
@@ -58,6 +58,25 @@
org.springframework.boot
spring-boot-starter-jdbc
+
+ org.projectlombok
+ lombok
+ 1.18.12
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.10.11
+
+
+ org.springframework.data
+ spring-data-commons
+ 2.3.0.RELEASE
+
3.5.2
diff --git a/spring-caching/src/main/java/com/baeldung/springdatacaching/model/Book.java b/spring-caching/src/main/java/com/baeldung/springdatacaching/model/Book.java
new file mode 100644
index 0000000000..7de567f0db
--- /dev/null
+++ b/spring-caching/src/main/java/com/baeldung/springdatacaching/model/Book.java
@@ -0,0 +1,21 @@
+package com.baeldung.springdatacaching.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import java.util.UUID;
+
+@Data
+@Entity
+@NoArgsConstructor
+@AllArgsConstructor
+public class Book {
+
+ @Id
+ private UUID id;
+ private String title;
+
+}
diff --git a/spring-caching/src/main/java/com/baeldung/springdatacaching/repositories/BookRepository.java b/spring-caching/src/main/java/com/baeldung/springdatacaching/repositories/BookRepository.java
new file mode 100644
index 0000000000..4b40fd4060
--- /dev/null
+++ b/spring-caching/src/main/java/com/baeldung/springdatacaching/repositories/BookRepository.java
@@ -0,0 +1,15 @@
+package com.baeldung.springdatacaching.repositories;
+
+import com.baeldung.springdatacaching.model.Book;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface BookRepository extends CrudRepository {
+
+ @Cacheable(value = "books", unless = "#a0=='Foundation'")
+ Optional findFirstByTitle(String title);
+
+}
diff --git a/spring-caching/src/main/resources/schema.sql b/spring-caching/src/main/resources/schema.sql
index 5862499bc0..35d02bb916 100644
--- a/spring-caching/src/main/resources/schema.sql
+++ b/spring-caching/src/main/resources/schema.sql
@@ -1,3 +1,7 @@
+DROP TABLE ORDERDETAIL IF EXISTS;
+DROP TABLE ITEM IF EXISTS;
+DROP TABLE CUSTOMER IF EXISTS;
+
CREATE TABLE CUSTOMER(
CUSTOMERID INT PRIMARY KEY,
CUSTOMERNAME VARCHAR(250) NOT NULL
diff --git a/spring-caching/src/test/java/com/baeldung/springdatacaching/repositories/BookRepositoryCachingIntegrationTest.java b/spring-caching/src/test/java/com/baeldung/springdatacaching/repositories/BookRepositoryCachingIntegrationTest.java
new file mode 100644
index 0000000000..cd11d7cc4a
--- /dev/null
+++ b/spring-caching/src/test/java/com/baeldung/springdatacaching/repositories/BookRepositoryCachingIntegrationTest.java
@@ -0,0 +1,86 @@
+package com.baeldung.springdatacaching.repositories;
+
+import com.baeldung.springdatacaching.model.Book;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.util.AopTestUtils;
+
+import java.util.UUID;
+
+import static java.util.Optional.of;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@ContextConfiguration
+@ExtendWith(SpringExtension.class)
+public class BookRepositoryCachingIntegrationTest {
+
+ private static final Book DUNE = new Book(UUID.randomUUID(), "Dune");
+ private static final Book FOUNDATION = new Book(UUID.randomUUID(), "Foundation");
+
+ private BookRepository mock;
+
+ @Autowired
+ private BookRepository bookRepository;
+
+ @EnableCaching
+ @Configuration
+ public static class CachingTestConfig {
+
+ @Bean
+ public BookRepository bookRepositoryMockImplementation() {
+ return mock(BookRepository.class);
+ }
+
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager("books");
+ }
+
+ }
+
+ @BeforeEach
+ void setUp() {
+ mock = AopTestUtils.getTargetObject(bookRepository);
+
+ reset(mock);
+
+ when(mock.findFirstByTitle(eq("Foundation")))
+ .thenReturn(of(FOUNDATION));
+
+ when(mock.findFirstByTitle(eq("Dune")))
+ .thenReturn(of(DUNE))
+ .thenThrow(new RuntimeException("Book should be cached!"));
+ }
+
+ @Test
+ void givenCachedBook_whenFindByTitle_thenRepositoryShouldNotBeHit() {
+ assertEquals(of(DUNE), bookRepository.findFirstByTitle("Dune"));
+ verify(mock).findFirstByTitle("Dune");
+
+ assertEquals(of(DUNE), bookRepository.findFirstByTitle("Dune"));
+ assertEquals(of(DUNE), bookRepository.findFirstByTitle("Dune"));
+
+ verifyNoMoreInteractions(mock);
+ }
+
+ @Test
+ void givenNotCachedBook_whenFindByTitle_thenRepositoryShouldBeHit() {
+ assertEquals(of(FOUNDATION), bookRepository.findFirstByTitle("Foundation"));
+ assertEquals(of(FOUNDATION), bookRepository.findFirstByTitle("Foundation"));
+ assertEquals(of(FOUNDATION), bookRepository.findFirstByTitle("Foundation"));
+
+ verify(mock, times(3)).findFirstByTitle("Foundation");
+ }
+
+}
diff --git a/spring-caching/src/test/java/com/baeldung/springdatacaching/repositories/BookRepositoryIntegrationTest.java b/spring-caching/src/test/java/com/baeldung/springdatacaching/repositories/BookRepositoryIntegrationTest.java
new file mode 100644
index 0000000000..3d11f4ad0b
--- /dev/null
+++ b/spring-caching/src/test/java/com/baeldung/springdatacaching/repositories/BookRepositoryIntegrationTest.java
@@ -0,0 +1,58 @@
+package com.baeldung.springdatacaching.repositories;
+
+import com.baeldung.caching.boot.CacheApplication;
+import com.baeldung.springdatacaching.model.Book;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cache.CacheManager;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static java.util.Optional.empty;
+import static java.util.Optional.ofNullable;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@ExtendWith(SpringExtension.class)
+@EntityScan(basePackageClasses = Book.class)
+@SpringBootTest(classes = CacheApplication.class)
+@EnableJpaRepositories(basePackageClasses = BookRepository.class)
+public class BookRepositoryIntegrationTest {
+
+ @Autowired
+ CacheManager cacheManager;
+
+ @Autowired
+ BookRepository repository;
+
+ @BeforeEach
+ void setUp() {
+ repository.save(new Book(UUID.randomUUID(), "Dune"));
+ repository.save(new Book(UUID.randomUUID(), "Foundation"));
+ }
+
+ @Test
+ void givenBookThatShouldBeCached_whenFindByTitle_thenResultShouldBePutInCache() {
+ Optional dune = repository.findFirstByTitle("Dune");
+
+ assertEquals(dune, getCachedBook("Dune"));
+ }
+
+ @Test
+ void givenBookThatShouldNotBeCached_whenFindByTitle_thenResultShouldNotBePutInCache() {
+ repository.findFirstByTitle("Foundation");
+
+ assertEquals(empty(), getCachedBook("Foundation"));
+ }
+
+ private Optional getCachedBook(String title) {
+ return ofNullable(cacheManager.getCache("books")).map(c -> c.get(title, Book.class));
+ }
+
+}