refactor code and add integration tests

This commit is contained in:
saikat 2024-02-29 11:12:18 +05:30
parent 43a168a525
commit 33ed7196ba
5 changed files with 149 additions and 38 deletions

View File

@ -1,7 +1,7 @@
package com.baeldung.caching.multicache; package com.baeldung.caching.multicache;
import com.github.benmanes.caffeine.cache.Caffeine; 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.CacheManager;
import org.springframework.cache.annotation.AnnotationCacheOperationSource; import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.EnableCaching; 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.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory; 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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext;
@ -27,21 +25,6 @@ import java.util.Arrays;
@EnableCaching @EnableCaching
public class CacheConfig { 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 @Bean
@Primary @Primary
public CacheManager caffeineCacheManager() { public CacheManager caffeineCacheManager() {
@ -52,25 +35,26 @@ public class CacheConfig {
} }
@Bean @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 return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory()) .fromConnectionFactory(connectionFactory)
.withCacheConfiguration("customerCache", cacheConfiguration()) .withCacheConfiguration("customerCache", cacheConfiguration())
.build(); .build();
} }
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(redisHost);
redisStandaloneConfiguration.setPort(redisPort);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean @Bean
public RedisCacheConfiguration cacheConfiguration() { public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig() return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60)) .entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues() .disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
} }
@ -86,5 +70,4 @@ public class CacheConfig {
public CacheOperationSource cacheOperationSource() { public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource(); return new AnnotationCacheOperationSource();
} }
} }

View File

@ -17,9 +17,9 @@ public class CustomerCacheInterceptor extends CacheInterceptor {
protected Cache.ValueWrapper doGet(Cache cache, Object key) { protected Cache.ValueWrapper doGet(Cache cache, Object key) {
Cache.ValueWrapper existingCacheValue = super.doGet(cache, 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()); Cache caffeineCache = caffeineCacheManager.getCache(cache.getName());
if (existingCacheValue != null && caffeineCache != null && caffeineCache.get(key) == null) { if (caffeineCache != null) {
caffeineCache.putIfAbsent(key, existingCacheValue.get()); caffeineCache.putIfAbsent(key, existingCacheValue.get());
} }
} }
@ -27,4 +27,3 @@ public class CustomerCacheInterceptor extends CacheInterceptor {
return existingCacheValue; return existingCacheValue;
} }
} }

View File

@ -9,6 +9,6 @@ spring.jpa.hibernate.ddl-auto=update
#setting cache TTL #setting cache TTL
caching.spring.hotelListTTL=43200 caching.spring.hotelListTTL=43200
# Connection details # Connection details
spring.redis.host=localhost #spring.redis.host=localhost
spring.redis.port=6379 #spring.redis.port=6379
spring.main.allow-bean-definition-overriding=true spring.main.allow-bean-definition-overriding=true

View File

@ -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();
}
}
}

View File

@ -16,6 +16,7 @@ import redis.embedded.RedisServer;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -66,17 +67,17 @@ class ItemServiceCachingIntegrationTest {
private final RedisServer redisServer; private final RedisServer redisServer;
public EmbeddedRedisConfiguration() { public EmbeddedRedisConfiguration() throws IOException {
this.redisServer = new RedisServer(); this.redisServer = new RedisServer();
} }
@PostConstruct @PostConstruct
public void startRedis() { public void startRedis() throws IOException {
redisServer.start(); redisServer.start();
} }
@PreDestroy @PreDestroy
public void stopRedis() { public void stopRedis() throws IOException {
this.redisServer.stop(); this.redisServer.stop();
} }
} }