refactor code and add integration tests
This commit is contained in:
parent
43a168a525
commit
33ed7196ba
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue