[JAVA-31545] (#16222)

This commit is contained in:
panos-kakos
2024-03-26 00:06:53 +02:00
committed by GitHub
parent 0facf5ca65
commit c75b4b13fa
84 changed files with 7 additions and 7 deletions
@@ -0,0 +1,25 @@
package com.baeldung.allkeys.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baeldung.allkeys.service.SlowServiceWithCache;
@Configuration
@EnableCaching
public class AllKeysConfig {
@Bean
SlowServiceWithCache slowServiceWithCache() {
return new SlowServiceWithCache();
}
@Bean
CacheManager cacheManager() {
return new CaffeineCacheManager();
}
}
@@ -0,0 +1,11 @@
package com.baeldung.allkeys.service;
import org.springframework.cache.annotation.CachePut;
public class SlowServiceWithCache {
@CachePut(cacheNames = "slowServiceCache", key = "#name")
public String save(String name, String details) {
return details;
}
}
@@ -0,0 +1,33 @@
package com.baeldung.caching.redis;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import java.time.Duration;
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("itemCache",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)))
.withCacheConfiguration("customerCache",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)));
}
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}
@@ -0,0 +1,21 @@
package com.baeldung.caching.redis;
import java.io.Serializable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Item implements Serializable {
@Id
String id;
String description;
}
@@ -0,0 +1,19 @@
package com.baeldung.caching.redis;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AllArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/item/{id}")
public Item getItemById(@PathVariable String id) {
return itemService.getItemForId(id);
}
}
@@ -0,0 +1,6 @@
package com.baeldung.caching.redis;
import org.springframework.data.repository.CrudRepository;
public interface ItemRepository extends CrudRepository<Item, String> {
}
@@ -0,0 +1,19 @@
package com.baeldung.caching.redis;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
@Cacheable(value = "itemCache")
public Item getItemForId(String id) {
return itemRepository.findById(id)
.orElseThrow(RuntimeException::new);
}
}
@@ -0,0 +1,14 @@
package com.baeldung.caching.redis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class RedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
}
@@ -0,0 +1,13 @@
package com.baeldung.caching.ttl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class CachingTTLApplication {
public static void main(String[] args) {
SpringApplication.run(CachingTTLApplication.class, args);
}
}
@@ -0,0 +1,17 @@
package com.baeldung.caching.ttl.config;
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;
@Configuration
@EnableCaching
public class SpringCachingConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("hotels");
}
}
@@ -0,0 +1,29 @@
package com.baeldung.caching.ttl.controller;
import com.baeldung.caching.ttl.service.HotelService;
import com.baeldung.caching.ttl.model.Hotel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/hotel")
public class HotelController {
private final HotelService hotelService;
@Autowired
public HotelController(HotelService hotelService) {
this.hotelService = hotelService;
}
@GetMapping
@ResponseStatus(HttpStatus.OK)
public List<Hotel> getAllHotels() {
return hotelService.getAllHotels();
}
}
@@ -0,0 +1,26 @@
package com.baeldung.caching.ttl.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.LinkedHashMap;
import java.util.Map;
@ControllerAdvice
public class ControllerAdvisor extends ResponseEntityExceptionHandler {
@ExceptionHandler(ElementNotFoundException.class)
public ResponseEntity<Object> handleNodataFoundException(
ElementNotFoundException ex) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("status", 404);
body.put("error", "Not Found");
body.put("message", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
}
@@ -0,0 +1,13 @@
package com.baeldung.caching.ttl.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ElementNotFoundException extends RuntimeException {
private static final long serialVersionUID = -5218143265247846948L;
public ElementNotFoundException(String message) {
super(message);
}
}
@@ -0,0 +1,66 @@
package com.baeldung.caching.ttl.model;
import java.io.Serializable;
import java.util.Objects;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class City implements Serializable {
private static final long serialVersionUID = 3252591505029724236L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double cityCentreLattitude;
private double cityCentreLongitude;
public City() {}
public City(Long id, String name, double cityCentreLatitude, double cityCentreLongitude) {
this.id = id;
this.name = name;
this.cityCentreLattitude = cityCentreLatitude;
this.cityCentreLongitude = cityCentreLongitude;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public double getCityCentreLatitude() {
return cityCentreLattitude;
}
public double getCityCentreLongitude() {
return cityCentreLongitude;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
City city = (City) o;
if (Double.compare(city.cityCentreLattitude, cityCentreLattitude) != 0) return false;
if (Double.compare(city.cityCentreLongitude, cityCentreLongitude) != 0) return false;
if (!Objects.equals(id, city.id)) return false;
return Objects.equals(name, city.name);
}
}
@@ -0,0 +1,137 @@
package com.baeldung.caching.ttl.model;
import java.io.Serializable;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
@Entity
public class Hotel implements Serializable {
private static final long serialVersionUID = 5560221391479816650L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double rating;
@ManyToOne(fetch = FetchType.EAGER)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private City city;
private String address;
private double lattitude;
private double longitude;
private boolean deleted = false;
public Hotel() {}
public Hotel(
Long id,
String name,
Double rating,
City city,
String address,
double lattitude,
double longitude,
boolean deleted) {
this.id = id;
this.name = name;
this.rating = rating;
this.city = city;
this.address = address;
this.lattitude = lattitude;
this.longitude = longitude;
this.deleted = deleted;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getRating() {
return rating;
}
public void setRating(Double rating) {
this.rating = rating;
}
public City getCity() {
return city;
}
public void setCity(City city) {
this.city = city;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getLatitude() {
return lattitude;
}
public void setLatitude(double latitude) {
this.lattitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Hotel hotel = (Hotel) o;
if (Double.compare(hotel.lattitude, lattitude) != 0) return false;
if (Double.compare(hotel.longitude, longitude) != 0) return false;
if (deleted != hotel.deleted) return false;
if (!Objects.equals(id, hotel.id)) return false;
if (!Objects.equals(name, hotel.name)) return false;
if (!Objects.equals(rating, hotel.rating)) return false;
if (!Objects.equals(city, hotel.city)) return false;
return Objects.equals(address, hotel.address);
}
}
@@ -0,0 +1,6 @@
package com.baeldung.caching.ttl.repository;
import com.baeldung.caching.ttl.model.City;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CityRepository extends JpaRepository<City, Long> {}
@@ -0,0 +1,19 @@
package com.baeldung.caching.ttl.repository;
import com.baeldung.caching.ttl.model.Hotel;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface HotelRepository extends JpaRepository<Hotel, Long> {
default List<Hotel> getAllHotels() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return findAll();
}
}
@@ -0,0 +1,35 @@
package com.baeldung.caching.ttl.service;
import com.baeldung.caching.ttl.repository.HotelRepository;
import com.baeldung.caching.ttl.model.Hotel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class HotelService {
private final HotelRepository hotelRepository;
Logger logger = LoggerFactory.getLogger(HotelService.class);
HotelService(HotelRepository hotelRepository) {
this.hotelRepository = hotelRepository;
}
@Cacheable("hotels")
public List<Hotel> getAllHotels() {
return hotelRepository.getAllHotels();
}
@CacheEvict(value = "hotels", allEntries = true)
@Scheduled(fixedRateString = "${caching.spring.hotelListTTL}")
public void emptyHotelsCache() {
logger.info("emptying Hotels cache");
}
}
@@ -0,0 +1,16 @@
package com.baeldung.caching.ttl.service;
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.stereotype.Component;
import static java.util.Arrays.asList;
@Component
public class SpringCacheCustomizer implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(asList("hotels"));
}
}
@@ -0,0 +1,71 @@
package com.baeldung.caching.twolevelcache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
import java.util.Arrays;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
@Primary
public CacheManager caffeineCacheManager(CaffeineCache caffeineCache) {
SimpleCacheManager manager = new SimpleCacheManager();
manager.setCaches(Arrays.asList(caffeineCache));
return manager;
}
@Bean
public CaffeineCache caffeineCacheConfig() {
return new CaffeineCache("customerCache", Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(3))
.initialCapacity(1)
.maximumSize(2000)
.build());
}
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory, RedisCacheConfiguration redisCacheConfiguration) {
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory)
.withCacheConfiguration("customerCache", redisCacheConfiguration)
.build();
}
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
@Bean
public CacheInterceptor cacheInterceptor(CacheManager caffeineCacheManager, CacheOperationSource cacheOperationSource) {
CacheInterceptor interceptor = new CustomerCacheInterceptor(caffeineCacheManager);
interceptor.setCacheOperationSources(cacheOperationSource);
return interceptor;
}
@Bean
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
}
@@ -0,0 +1,25 @@
package com.baeldung.caching.twolevelcache;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Setter
public class Customer implements Serializable {
@Id
private String id;
private String name;
private String email;
}
@@ -0,0 +1,29 @@
package com.baeldung.caching.twolevelcache;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.data.redis.cache.RedisCache;
public class CustomerCacheInterceptor extends CacheInterceptor {
private final CacheManager caffeineCacheManager;
public CustomerCacheInterceptor(CacheManager caffeineCacheManager) {
this.caffeineCacheManager = caffeineCacheManager;
}
@Override
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
Cache.ValueWrapper existingCacheValue = super.doGet(cache, key);
if (existingCacheValue != null && cache.getClass() == RedisCache.class) {
Cache caffeineCache = caffeineCacheManager.getCache(cache.getName());
if (caffeineCache != null) {
caffeineCache.putIfAbsent(key, existingCacheValue.get());
}
}
return existingCacheValue;
}
}
@@ -0,0 +1,6 @@
package com.baeldung.caching.twolevelcache;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, String> {
}
@@ -0,0 +1,26 @@
package com.baeldung.caching.twolevelcache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
private final CustomerRepository customerRepository;
@Autowired
public CustomerService(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Caching(cacheable = {
@Cacheable(cacheNames = "customerCache", cacheManager = "caffeineCacheManager"),
@Cacheable(cacheNames = "customerCache", cacheManager = "redisCacheManager")
})
public Customer getCustomer(String id) {
return customerRepository.findById(id)
.orElseThrow(RuntimeException::new);
}
}
@@ -0,0 +1,12 @@
package com.baeldung.caching.twolevelcache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TwoLevelCacheApplication {
public static void main(String[] args) {
SpringApplication.run(TwoLevelCacheApplication.class, args);
}
}
@@ -0,0 +1,14 @@
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# Enabling H2 Console
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.hibernate.ddl-auto=update
#setting cache TTL
caching.spring.hotelListTTL=43200
# Connection details
#spring.redis.host=localhost
#spring.redis.port=6379
spring.main.allow-bean-definition-overriding=true
@@ -0,0 +1,67 @@
DROP TABLE IF EXISTS ITEM;
create table ITEM
(
ID CHARACTER VARYING not null,
DESCRIPTION CHARACTER VARYING,
constraint ITEM_PK
primary key (ID)
);
DROP TABLE IF EXISTS HOTEL;
DROP TABLE IF EXISTS CITY;
create table CITY
(
id bigint,
name varchar,
city_centre_lattitude double,
city_centre_longitude double,
constraint city_pk
primary key (id)
);
create table hotel
(
id bigint auto_increment,
name varchar,
deleted boolean,
rating double,
city_id bigint,
address varchar,
lattitude varchar,
longitude varchar,
constraint hotel_pk
primary key (id),
constraint "hotel_CITY_null_fk"
foreign key (city_id) references CITY (id)
);
INSERT INTO ITEM VALUES('abc','ITEM1');
INSERT INTO city(id, name, city_centre_lattitude, city_centre_longitude)
VALUES (1, 'Amsterdam', 52.368780, 4.903303);
INSERT INTO city(id, name, city_centre_lattitude, city_centre_longitude)
VALUES (2, 'Manchester', 53.481062, -2.237706);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Monaghan Hotel', false, 9.2, 1, 'Weesperbuurt en Plantage', 52.364799, 4.908971);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('The Thornton Council Hotel', false, 6.3, 1, 'Waterlooplein', 52.3681563, 4.9010029);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('McZoe Trescothiks Hotel', false, 9.8, 1, 'Oude Stad, Harlem', 52.379577, 4.633547);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Stay Schmtay Hotel', false, 8.7, 1, 'Jan van Galenstraat', 52.3756755, 4.8668628);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Fitting Image Hotel', false, NULL, 1, 'Staatsliedenbuurt', 52.380936, 4.8708297);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Raymond of Amsterdam Hotel', false, NULL, 1, '22 High Avenue', 52.3773989, 4.8846443);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('201 Deansgate Hotel', false, 7.3, 2, '201 Deansgate', 53.4788305, -2.2484721);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Fountain Street Hotel', true, 3.0, 2, '35 Fountain Street', 53.4811298, -2.2402227);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Sunlight House', false, 4.3, 2, 'Little Quay St', 53.4785129, -2.2505943);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('St Georges House', false, 9.6, 2, '56 Peter St', 53.477822, -2.2462002);
INSERT INTO hotel(name, deleted, rating, city_id, address, lattitude, longitude)
VALUES ('Marriot Bonvoy', false, 9.6, 1, 'Hans Zimmerstraat', 53.477872, -2.2462003);
@@ -0,0 +1,44 @@
package com.baeldung.caching.allkeys.boot;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.baeldung.allkeys.config.AllKeysConfig;
import com.baeldung.allkeys.service.SlowServiceWithCache;
import com.github.benmanes.caffeine.cache.Cache;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = AllKeysConfig.class)
public class GetAllCacheKeysIntegrationTest {
@Autowired
private SlowServiceWithCache slowServiceWithCache;
@Autowired
private CacheManager cacheManager;
@Test
public void givenCaffeineCacheCachingSlowCalls_whenCacheManagerProperlyCasted_thenAllKeysAreAccessible() {
slowServiceWithCache.save("first", "some-value-first");
slowServiceWithCache.save("second", "other-value-second");
Cache<Object, Object> caffeine = getNativeCaffeineCacheForSlowService();
assertThat(caffeine.asMap().keySet()).containsOnly("first", "second");
}
private Cache<Object, Object> getNativeCaffeineCacheForSlowService() {
CaffeineCacheManager caffeineCacheManager = (CaffeineCacheManager) cacheManager;
CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache("slowServiceCache");
return cache.getNativeCache();
}
}
@@ -0,0 +1,87 @@
package com.baeldung.caching.redis;
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 org.junit.jupiter.api.Disabled;
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 jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional;
@Import({ CacheConfig.class, ItemService.class })
@ExtendWith(SpringExtension.class)
@ImportAutoConfiguration(classes = { CacheAutoConfiguration.class, RedisAutoConfiguration.class })
@EnableCaching
@Disabled("This will be fixed.")
class ItemServiceCachingIntegrationTest {
private static final String AN_ID = "id-1";
private static final String A_DESCRIPTION = "an item";
@MockBean
private ItemRepository mockItemRepository;
@Autowired
private ItemService itemService;
@Autowired
private CacheManager cacheManager;
@Test
void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
Item anItem = new Item(AN_ID, A_DESCRIPTION);
given(mockItemRepository.findById(AN_ID))
.willReturn(Optional.of(anItem));
Item itemCacheMiss = itemService.getItemForId(AN_ID);
Item itemCacheHit = itemService.getItemForId(AN_ID);
assertThat(itemCacheMiss).isEqualTo(anItem);
assertThat(itemCacheHit).isEqualTo(anItem);
verify(mockItemRepository, times(1)).findById(AN_ID);
assertThat(itemFromCache()).isEqualTo(anItem);
}
private Object itemFromCache() {
return cacheManager.getCache("itemCache").get(AN_ID).get();
}
@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();
}
}
}
@@ -0,0 +1,43 @@
package com.baeldung.caching.ttl;
import com.baeldung.caching.ttl.repository.HotelRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@AutoConfigureMockMvc
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:data.sql")
@SlowTest
class HotelControllerIntegrationTest {
@Autowired private MockMvc mockMvc;
@Autowired private HotelRepository repository;
@Test
@DisplayName("When all hotels requested then request is successful")
void whenAllHotelsRequested_thenRequestIsSuccessful() throws Exception {
mockMvc
.perform(get("/hotel"))
.andExpect(status().isOk());
}
@Test
@DisplayName("When all hotels are requested then they correct number of hotels is returned")
void whenAllHotelsRequested_thenReturnAllHotels() throws Exception {
mockMvc
.perform(get("/hotel"))
.andExpect(jsonPath("$", hasSize((int) repository.findAll().stream().count())));
}
}
@@ -0,0 +1,4 @@
package com.baeldung.caching.ttl;
public @interface SlowTest {
}
@@ -0,0 +1,124 @@
package com.baeldung.caching.twolevelcache;
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 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 jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@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 givenCustomerIsPresent_whenGetCustomerCalled_thenReturnCustomerAndCacheIt() {
String CUSTOMER_ID = "100";
Customer customer = new Customer(CUSTOMER_ID, "test", "test@mail.com");
given(customerRepository.findById(CUSTOMER_ID)).willReturn(Optional.of(customer));
Customer customerCacheMiss = customerService.getCustomer(CUSTOMER_ID);
assertThat(customerCacheMiss).isEqualTo(customer);
verify(customerRepository, times(1)).findById(CUSTOMER_ID);
assertThat(customerFromCaffeineCache(CUSTOMER_ID)).isEqualTo(customer);
assertThat(customerFromRedisCache(CUSTOMER_ID)).isEqualTo(customer);
}
@Test
void givenCustomerIsPresent_whenGetCustomerCalledTwice_thenReturnCustomerAndCacheIt() {
String CUSTOMER_ID = "101";
Customer customer = new Customer(CUSTOMER_ID, "test", "test@mail.com");
given(customerRepository.findById(CUSTOMER_ID)).willReturn(Optional.of(customer));
Customer customerCacheMiss = customerService.getCustomer(CUSTOMER_ID);
Customer customerCacheHit = customerService.getCustomer(CUSTOMER_ID);
assertThat(customerCacheMiss).isEqualTo(customer);
assertThat(customerCacheHit).isEqualTo(customer);
verify(customerRepository, times(1)).findById(CUSTOMER_ID);
assertThat(customerFromCaffeineCache(CUSTOMER_ID)).isEqualTo(customer);
assertThat(customerFromRedisCache(CUSTOMER_ID)).isEqualTo(customer);
}
@Test
void givenCustomerIsPresent_whenGetCustomerCalledTwiceAndFirstCacheExpired_thenReturnCustomerAndCacheIt() throws InterruptedException {
String CUSTOMER_ID = "102";
Customer customer = new Customer(CUSTOMER_ID, "test", "test@mail.com");
given(customerRepository.findById(CUSTOMER_ID)).willReturn(Optional.of(customer));
Customer customerCacheMiss = customerService.getCustomer(CUSTOMER_ID);
TimeUnit.SECONDS.sleep(3);
assertThat(customerFromCaffeineCache(CUSTOMER_ID)).isEqualTo(null);
Customer customerCacheHit = customerService.getCustomer(CUSTOMER_ID);
verify(customerRepository, times(1)).findById(CUSTOMER_ID);
assertThat(customerCacheMiss).isEqualTo(customer);
assertThat(customerCacheHit).isEqualTo(customer);
assertThat(customerFromCaffeineCache(CUSTOMER_ID)).isEqualTo(customer);
assertThat(customerFromRedisCache(CUSTOMER_ID)).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();
}
}
}
@@ -0,0 +1,15 @@
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# Enabling H2 Console
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
caching.spring.hotelListTTL=43200
# Connection details
#spring.redis.host=localhost
#spring.redis.port=6379
server.port=8000