From 33ed7196ba17326ebaf589c4842d8eadc3ad5a7a Mon Sep 17 00:00:00 2001 From: saikat Date: Thu, 29 Feb 2024 11:12:18 +0530 Subject: [PATCH] refactor code and add integration tests --- .../caching/multicache/CacheConfig.java | 43 ++---- .../multicache/CustomerCacheInterceptor.java | 5 +- .../src/main/resources/application.properties | 4 +- ...CustomerServiceCachingIntegrationTest.java | 128 ++++++++++++++++++ .../ItemServiceCachingIntegrationTest.java | 7 +- 5 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/multicache/CustomerServiceCachingIntegrationTest.java diff --git a/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CacheConfig.java b/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CacheConfig.java index 29419f4c12..22b223066a 100644 --- a/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CacheConfig.java +++ b/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CacheConfig.java @@ -1,7 +1,7 @@ package com.baeldung.caching.multicache; import com.github.benmanes.caffeine.cache.Caffeine; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.AnnotationCacheOperationSource; import org.springframework.cache.annotation.EnableCaching; @@ -15,8 +15,6 @@ import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStandaloneConfiguration; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; @@ -27,21 +25,6 @@ import java.util.Arrays; @EnableCaching public class CacheConfig { - @Value("${spring.redis.host}") - private String redisHost; - - @Value("${spring.redis.port}") - private int redisPort; - - @Bean - public CaffeineCache caffeineCacheConfig() { - return new CaffeineCache("customerCache", Caffeine.newBuilder() - .expireAfterWrite(Duration.ofMinutes(1)) - .initialCapacity(1) - .maximumSize(2000) - .build()); - } - @Bean @Primary public CacheManager caffeineCacheManager() { @@ -52,25 +35,26 @@ public class CacheConfig { } @Bean - public CacheManager redisCacheManager() { + public CaffeineCache caffeineCacheConfig() { + return new CaffeineCache("customerCache", Caffeine.newBuilder() + .expireAfterWrite(Duration.ofSeconds(3)) + .initialCapacity(1) + .maximumSize(2000) + .build()); + } + + @Bean + public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) { return RedisCacheManager.RedisCacheManagerBuilder - .fromConnectionFactory(redisConnectionFactory()) + .fromConnectionFactory(connectionFactory) .withCacheConfiguration("customerCache", cacheConfiguration()) .build(); } - @Bean - public RedisConnectionFactory redisConnectionFactory() { - RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); - redisStandaloneConfiguration.setHostName(redisHost); - redisStandaloneConfiguration.setPort(redisPort); - return new LettuceConnectionFactory(redisStandaloneConfiguration); - } - @Bean public RedisCacheConfiguration cacheConfiguration() { return RedisCacheConfiguration.defaultCacheConfig() - .entryTtl(Duration.ofMinutes(60)) + .entryTtl(Duration.ofMinutes(5)) .disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); } @@ -86,5 +70,4 @@ public class CacheConfig { public CacheOperationSource cacheOperationSource() { return new AnnotationCacheOperationSource(); } - } \ No newline at end of file diff --git a/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CustomerCacheInterceptor.java b/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CustomerCacheInterceptor.java index 2ca1d2b2de..4c9a981489 100644 --- a/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CustomerCacheInterceptor.java +++ b/spring-boot-modules/spring-caching-2/src/main/java/com/baeldung/caching/multicache/CustomerCacheInterceptor.java @@ -17,9 +17,9 @@ public class CustomerCacheInterceptor extends CacheInterceptor { protected Cache.ValueWrapper doGet(Cache cache, Object key) { Cache.ValueWrapper existingCacheValue = super.doGet(cache, key); - if (cache.getClass() == RedisCache.class) { + if (existingCacheValue != null && cache.getClass() == RedisCache.class) { Cache caffeineCache = caffeineCacheManager.getCache(cache.getName()); - if (existingCacheValue != null && caffeineCache != null && caffeineCache.get(key) == null) { + if (caffeineCache != null) { caffeineCache.putIfAbsent(key, existingCacheValue.get()); } } @@ -27,4 +27,3 @@ public class CustomerCacheInterceptor extends CacheInterceptor { return existingCacheValue; } } - diff --git a/spring-boot-modules/spring-caching-2/src/main/resources/application.properties b/spring-boot-modules/spring-caching-2/src/main/resources/application.properties index 97e4f97fcb..49bd715e43 100644 --- a/spring-boot-modules/spring-caching-2/src/main/resources/application.properties +++ b/spring-boot-modules/spring-caching-2/src/main/resources/application.properties @@ -9,6 +9,6 @@ spring.jpa.hibernate.ddl-auto=update #setting cache TTL caching.spring.hotelListTTL=43200 # Connection details -spring.redis.host=localhost -spring.redis.port=6379 +#spring.redis.host=localhost +#spring.redis.port=6379 spring.main.allow-bean-definition-overriding=true diff --git a/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/multicache/CustomerServiceCachingIntegrationTest.java b/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/multicache/CustomerServiceCachingIntegrationTest.java new file mode 100644 index 0000000000..919c32fc66 --- /dev/null +++ b/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/multicache/CustomerServiceCachingIntegrationTest.java @@ -0,0 +1,128 @@ +package com.baeldung.caching.multicache; + +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.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import redis.embedded.RedisServer; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@Import({ CacheConfig.class,CustomerService.class }) +@ExtendWith(SpringExtension.class) +@ImportAutoConfiguration(classes = { CacheAutoConfiguration.class, RedisAutoConfiguration.class }) +@EnableCaching +class CustomerServiceCachingIntegrationTest { + + @MockBean + private CustomerRepository customerRepository; + + @Autowired + private CustomerService customerService; + + @Autowired + private CacheManager redisCacheManager; + + @Autowired + private CacheManager caffeineCacheManager; + + @Test + void givenCustomerIsPresentInDb_whenFindCustomerById_thenCustomerReturnedFromDb_And_Cached() { + Customer customer = new Customer("100", "test", "test@mail.com"); + given(customerRepository.getCustomerById("100")) + .willReturn(customer); + + Customer customerCacheMiss = customerService.getCustomer("100"); + + assertThat(customerCacheMiss).isEqualTo(customer); + verify(customerRepository, times(1)).getCustomerById("100"); + assertThat(customerFromRedisCache("100")).isEqualTo(customer); + assertThat(customerFromCaffeineCache("100")).isEqualTo(customer); + } + + @Test + void givenCustomerIsPresentInDb_whenFindCustomerById_CalledTwice_thenCustomerReturnedFromDb_And_Cached() { + Customer customer = new Customer("101", "test", "test@mail.com"); + given(customerRepository.getCustomerById("101")) + .willReturn(customer); + + Customer customerCacheMiss = customerService.getCustomer("101"); + Customer customerCacheHit = customerService.getCustomer("101"); + + assertThat(customerCacheMiss).isEqualTo(customer); + assertThat(customerCacheHit).isEqualTo(customer); + + verify(customerRepository, times(1)).getCustomerById("101"); + assertThat(customerFromRedisCache("101")).isEqualTo(customer); + assertThat(customerFromCaffeineCache("101")).isEqualTo(customer); + } + + @Test + void givenCustomerIsPresentInDb_whenFindCustomerById_CalledThrice_thenCustomerReturnedFromDBFirst_ThenFromCache() throws InterruptedException { + Customer customer = new Customer("102", "test", "test@mail.com"); + given(customerRepository.getCustomerById("102")) + .willReturn(customer); + + Customer customerCacheMiss = customerService.getCustomer("102"); + Customer customerCacheHit = customerService.getCustomer("102"); + + TimeUnit.SECONDS.sleep(4); + + assertThat(customerFromCaffeineCache("102")).isEqualTo(null); + Customer customerCacheHitAgain = customerService.getCustomer("102"); + + verify(customerRepository, times(1)).getCustomerById("102"); + assertThat(customerCacheMiss).isEqualTo(customer); + assertThat(customerCacheHit).isEqualTo(customer); + assertThat(customerCacheHitAgain).isEqualTo(customer); + assertThat(customerFromRedisCache("102")).isEqualTo(customer); + assertThat(customerFromCaffeineCache("102")).isEqualTo(customer); + } + + private Object customerFromRedisCache(String key) { + return redisCacheManager.getCache("customerCache").get(key) != null ? + redisCacheManager.getCache("customerCache").get(key).get() : null; + } + + private Object customerFromCaffeineCache(String key) { + return caffeineCacheManager.getCache("customerCache").get(key) != null ? + caffeineCacheManager.getCache("customerCache").get(key).get() : null; + } + + @TestConfiguration + static class EmbeddedRedisConfiguration { + + private final RedisServer redisServer; + + public EmbeddedRedisConfiguration() throws IOException { + this.redisServer = new RedisServer(); + } + + @PostConstruct + public void startRedis() throws IOException { + redisServer.start(); + } + + @PreDestroy + public void stopRedis() throws IOException { + this.redisServer.stop(); + } + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java b/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java index 291e729fb9..b070528bf6 100644 --- a/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java +++ b/spring-boot-modules/spring-caching-2/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java @@ -16,6 +16,7 @@ import redis.embedded.RedisServer; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.io.IOException; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -66,17 +67,17 @@ class ItemServiceCachingIntegrationTest { private final RedisServer redisServer; - public EmbeddedRedisConfiguration() { + public EmbeddedRedisConfiguration() throws IOException { this.redisServer = new RedisServer(); } @PostConstruct - public void startRedis() { + public void startRedis() throws IOException { redisServer.start(); } @PreDestroy - public void stopRedis() { + public void stopRedis() throws IOException { this.redisServer.stop(); } }