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)); + } + +}