From 37526ebd0166bea0bf4a7ccc4ff1021891291111 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Fri, 9 Sep 2022 00:57:48 +0300 Subject: [PATCH] BAEL-5696: Adds TTL configuration for caching (#12700) Co-authored-by: Abdul Wahab --- pom.xml | 3 +- spring-caching-2/{ => redis}/README.md | 0 spring-caching-2/{ => redis}/pom.xml | 2 +- .../baeldung/caching/redis/CacheConfig.java | 0 .../java/com/baeldung/caching/redis/Item.java | 0 .../caching/redis/ItemController.java | 0 .../caching/redis/ItemRepository.java | 0 .../baeldung/caching/redis/ItemService.java | 0 .../caching/redis/RedisCacheApplication.java | 0 .../src/main/resources/application.properties | 0 .../{ => redis}/src/main/resources/data.sql | 0 .../ItemServiceCachingIntegrationTest.java | 0 .../src/test/resources/logback-test.xml | 0 spring-caching-2/ttl/pom.xml | 49 +++++++ .../com/baeldung/caching/ttl/Application.java | 13 ++ .../ttl/config/GuavaCachingConfig.java | 34 +++++ .../ttl/config/SpringCachingConfig.java | 17 +++ .../ttl/controller/HotelController.java | 36 +++++ .../ttl/exception/ControllerAdvisor.java | 26 ++++ .../exception/ElementNotFoundException.java | 13 ++ .../com/baeldung/caching/ttl/model/City.java | 65 +++++++++ .../com/baeldung/caching/ttl/model/Hotel.java | 131 ++++++++++++++++++ .../ttl/repository/CityRepository.java | 6 + .../ttl/repository/HotelRepository.java | 28 ++++ .../ttl/service/GuavaCacheCustomizer.java | 21 +++ .../caching/ttl/service/HotelService.java | 52 +++++++ .../ttl/service/SpringCacheCustomizer.java | 16 +++ .../ttl/src/main/resources/application.yml | 15 ++ .../ttl/src/main/resources/data.sql | 36 +++++ .../HotelControllerIntegrationTest.java | 71 ++++++++++ .../java/com/booking/testing/SlowTest.java | 4 + .../ttl/src/test/resources/application.yml | 15 ++ 32 files changed, 651 insertions(+), 2 deletions(-) rename spring-caching-2/{ => redis}/README.md (100%) rename spring-caching-2/{ => redis}/pom.xml (97%) rename spring-caching-2/{ => redis}/src/main/java/com/baeldung/caching/redis/CacheConfig.java (100%) rename spring-caching-2/{ => redis}/src/main/java/com/baeldung/caching/redis/Item.java (100%) rename spring-caching-2/{ => redis}/src/main/java/com/baeldung/caching/redis/ItemController.java (100%) rename spring-caching-2/{ => redis}/src/main/java/com/baeldung/caching/redis/ItemRepository.java (100%) rename spring-caching-2/{ => redis}/src/main/java/com/baeldung/caching/redis/ItemService.java (100%) rename spring-caching-2/{ => redis}/src/main/java/com/baeldung/caching/redis/RedisCacheApplication.java (100%) rename spring-caching-2/{ => redis}/src/main/resources/application.properties (100%) rename spring-caching-2/{ => redis}/src/main/resources/data.sql (100%) rename spring-caching-2/{ => redis}/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java (100%) rename spring-caching-2/{ => redis}/src/test/resources/logback-test.xml (100%) create mode 100644 spring-caching-2/ttl/pom.xml create mode 100755 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/Application.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/GuavaCachingConfig.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/SpringCachingConfig.java create mode 100755 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/controller/HotelController.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ControllerAdvisor.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ElementNotFoundException.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/City.java create mode 100755 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/Hotel.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/CityRepository.java create mode 100755 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/HotelRepository.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/GuavaCacheCustomizer.java create mode 100755 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/HotelService.java create mode 100644 spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/SpringCacheCustomizer.java create mode 100644 spring-caching-2/ttl/src/main/resources/application.yml create mode 100644 spring-caching-2/ttl/src/main/resources/data.sql create mode 100644 spring-caching-2/ttl/src/test/java/com/baeldung/caching/ttl/controller/HotelControllerIntegrationTest.java create mode 100644 spring-caching-2/ttl/src/test/java/com/booking/testing/SlowTest.java create mode 100644 spring-caching-2/ttl/src/test/resources/application.yml diff --git a/pom.xml b/pom.xml index 6b1ed206bd..3c8744d956 100644 --- a/pom.xml +++ b/pom.xml @@ -559,7 +559,8 @@ spring-boot-rest spring-caching - spring-caching-2 + spring-caching-2/redis + spring-caching-2/ttl spring-cloud-modules diff --git a/spring-caching-2/README.md b/spring-caching-2/redis/README.md similarity index 100% rename from spring-caching-2/README.md rename to spring-caching-2/redis/README.md diff --git a/spring-caching-2/pom.xml b/spring-caching-2/redis/pom.xml similarity index 97% rename from spring-caching-2/pom.xml rename to spring-caching-2/redis/pom.xml index 60e4873a3c..a772a7b031 100644 --- a/spring-caching-2/pom.xml +++ b/spring-caching-2/redis/pom.xml @@ -12,7 +12,7 @@ com.baeldung parent-boot-2 0.0.1-SNAPSHOT - ../parent-boot-2 + ../../parent-boot-2/pom.xml diff --git a/spring-caching-2/src/main/java/com/baeldung/caching/redis/CacheConfig.java b/spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/CacheConfig.java similarity index 100% rename from spring-caching-2/src/main/java/com/baeldung/caching/redis/CacheConfig.java rename to spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/CacheConfig.java diff --git a/spring-caching-2/src/main/java/com/baeldung/caching/redis/Item.java b/spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/Item.java similarity index 100% rename from spring-caching-2/src/main/java/com/baeldung/caching/redis/Item.java rename to spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/Item.java diff --git a/spring-caching-2/src/main/java/com/baeldung/caching/redis/ItemController.java b/spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/ItemController.java similarity index 100% rename from spring-caching-2/src/main/java/com/baeldung/caching/redis/ItemController.java rename to spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/ItemController.java diff --git a/spring-caching-2/src/main/java/com/baeldung/caching/redis/ItemRepository.java b/spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/ItemRepository.java similarity index 100% rename from spring-caching-2/src/main/java/com/baeldung/caching/redis/ItemRepository.java rename to spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/ItemRepository.java diff --git a/spring-caching-2/src/main/java/com/baeldung/caching/redis/ItemService.java b/spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/ItemService.java similarity index 100% rename from spring-caching-2/src/main/java/com/baeldung/caching/redis/ItemService.java rename to spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/ItemService.java diff --git a/spring-caching-2/src/main/java/com/baeldung/caching/redis/RedisCacheApplication.java b/spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/RedisCacheApplication.java similarity index 100% rename from spring-caching-2/src/main/java/com/baeldung/caching/redis/RedisCacheApplication.java rename to spring-caching-2/redis/src/main/java/com/baeldung/caching/redis/RedisCacheApplication.java diff --git a/spring-caching-2/src/main/resources/application.properties b/spring-caching-2/redis/src/main/resources/application.properties similarity index 100% rename from spring-caching-2/src/main/resources/application.properties rename to spring-caching-2/redis/src/main/resources/application.properties diff --git a/spring-caching-2/src/main/resources/data.sql b/spring-caching-2/redis/src/main/resources/data.sql similarity index 100% rename from spring-caching-2/src/main/resources/data.sql rename to spring-caching-2/redis/src/main/resources/data.sql diff --git a/spring-caching-2/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java b/spring-caching-2/redis/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java similarity index 100% rename from spring-caching-2/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java rename to spring-caching-2/redis/src/test/java/com/baeldung/caching/redis/ItemServiceCachingIntegrationTest.java diff --git a/spring-caching-2/src/test/resources/logback-test.xml b/spring-caching-2/redis/src/test/resources/logback-test.xml similarity index 100% rename from spring-caching-2/src/test/resources/logback-test.xml rename to spring-caching-2/redis/src/test/resources/logback-test.xml diff --git a/spring-caching-2/ttl/pom.xml b/spring-caching-2/ttl/pom.xml new file mode 100644 index 0000000000..b7edf90c44 --- /dev/null +++ b/spring-caching-2/ttl/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + spring-caching-3 + 0.1-SNAPSHOT + spring-caching-3 + + + org.springframework.boot + spring-boot-starter-parent + 2.3.4.RELEASE + + + + + org.springframework.data + spring-data-commons + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + com.google.guava + guava + 30.0-jre + + + \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/Application.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/Application.java new file mode 100755 index 0000000000..b8f337bcf7 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/Application.java @@ -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 Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/GuavaCachingConfig.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/GuavaCachingConfig.java new file mode 100644 index 0000000000..bfd0c6ce1e --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/GuavaCachingConfig.java @@ -0,0 +1,34 @@ +package com.baeldung.caching.ttl.config; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class GuavaCachingConfig { + + private Cache cache; + + Logger logger = LoggerFactory.getLogger(GuavaCachingConfig.class); + + public GuavaCachingConfig(int expiryDuration, TimeUnit timeUnit) { + cache = CacheBuilder.newBuilder() + .expireAfterWrite(expiryDuration, timeUnit) + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .build(); + } + + public T get(Long key) { + return cache.getIfPresent(key); + } + + public void add(Long key, T value) { + if(key != null && value != null) { + cache.put(key, value); + logger.info( + String.format("A %s record stored in Cache with key: %s", value.getClass().getSimpleName(), key)); + } + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/SpringCachingConfig.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/SpringCachingConfig.java new file mode 100644 index 0000000000..555933c0c1 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/config/SpringCachingConfig.java @@ -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"); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/controller/HotelController.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/controller/HotelController.java new file mode 100755 index 0000000000..f1a90eb950 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/controller/HotelController.java @@ -0,0 +1,36 @@ +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.PathVariable; +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 getAllHotels() { + return hotelService.getAllHotels(); + } + + @GetMapping(value = "/{id}") + @ResponseStatus(HttpStatus.OK) + public Hotel getHotelById(@PathVariable Long id) { + return hotelService.getHotelById(id); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ControllerAdvisor.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ControllerAdvisor.java new file mode 100644 index 0000000000..519f147929 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ControllerAdvisor.java @@ -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 handleNodataFoundException( + ElementNotFoundException ex) { + + Map body = new LinkedHashMap<>(); + body.put("status", 404); + body.put("error", "Not Found"); + body.put("message", ex.getMessage()); + + return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ElementNotFoundException.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ElementNotFoundException.java new file mode 100644 index 0000000000..7a2d995a9a --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/exception/ElementNotFoundException.java @@ -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); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/City.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/City.java new file mode 100644 index 0000000000..88902d7e05 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/City.java @@ -0,0 +1,65 @@ +package com.baeldung.caching.ttl.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.io.Serializable; +import java.util.Objects; + +@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 cityCentreLatitude; + private double cityCentreLongitude; + + public City() {} + + public City(Long id, String name, double cityCentreLatitude, double cityCentreLongitude) { + this.id = id; + this.name = name; + this.cityCentreLatitude = 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 cityCentreLatitude; + } + + 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.cityCentreLatitude, cityCentreLatitude) != 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); + } + +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/Hotel.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/Hotel.java new file mode 100755 index 0000000000..ffb4f298c8 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/model/Hotel.java @@ -0,0 +1,131 @@ +package com.baeldung.caching.ttl.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@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 latitude; + private double longitude; + private boolean deleted = false; + + public Hotel() {} + + public Hotel( + Long id, + String name, + Double rating, + City city, + String address, + double latitude, + double longitude, + boolean deleted) { + this.id = id; + this.name = name; + this.rating = rating; + this.city = city; + this.address = address; + this.latitude = latitude; + 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 latitude; + } + + public void setLatitude(double latitude) { + this.latitude = 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.latitude, latitude) != 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); + } + +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/CityRepository.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/CityRepository.java new file mode 100644 index 0000000000..3cf3f29e5f --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/CityRepository.java @@ -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 {} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/HotelRepository.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/HotelRepository.java new file mode 100755 index 0000000000..6ce5ca9e76 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/repository/HotelRepository.java @@ -0,0 +1,28 @@ +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 { + + default List getAllHotels() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return findAll(); + } + + default Optional getHotelById(Long hotelId) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return findById(hotelId); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/GuavaCacheCustomizer.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/GuavaCacheCustomizer.java new file mode 100644 index 0000000000..c64f0ea5f5 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/GuavaCacheCustomizer.java @@ -0,0 +1,21 @@ +package com.baeldung.caching.ttl.service; + +import com.baeldung.caching.ttl.config.GuavaCachingConfig; +import com.baeldung.caching.ttl.model.Hotel; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +public class GuavaCacheCustomizer { + + @Value("${caching.guava.hotelItemTTL}") + Integer hotelItemTTL; + + @Bean + public GuavaCachingConfig hotelGuavaCacheStore() { + return new GuavaCachingConfig<>(hotelItemTTL, TimeUnit.MILLISECONDS); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/HotelService.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/HotelService.java new file mode 100755 index 0000000000..c69d371456 --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/HotelService.java @@ -0,0 +1,52 @@ +package com.baeldung.caching.ttl.service; + +import com.baeldung.caching.ttl.repository.HotelRepository; +import com.baeldung.caching.ttl.config.GuavaCachingConfig; +import com.baeldung.caching.ttl.exception.ElementNotFoundException; +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; + private final GuavaCachingConfig hotelGuavaCachingConfig; + Logger logger = LoggerFactory.getLogger(HotelService.class); + + HotelService(HotelRepository hotelRepository, GuavaCachingConfig hotelGuavaCachingConfig) { + this.hotelRepository = hotelRepository; + this.hotelGuavaCachingConfig = hotelGuavaCachingConfig; + } + + @Cacheable("hotels") + public List getAllHotels() { + return hotelRepository.getAllHotels(); + } + + @CacheEvict(value = "hotels", allEntries = true) + @Scheduled(fixedRateString = "${caching.spring.hotelListTTL}") + public void emptyHotelsCache() { + logger.info("emptying Hotels cache"); + } + + + public Hotel getHotelById(Long hotelId) { + if (hotelGuavaCachingConfig.get(hotelId) != null) { + logger.info(String.format("hotel with id: %s found in cache", hotelId)); + return hotelGuavaCachingConfig.get(hotelId); + } + logger.info(String.format("hotel with id: %s is being searched in DB", hotelId)); + Hotel hotel = hotelRepository.getHotelById(hotelId) + .orElseThrow(() -> new ElementNotFoundException(String.format("Hotel with id %s not found", hotelId))); + hotelGuavaCachingConfig.add(hotelId, hotel); + return hotel; + } + +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/SpringCacheCustomizer.java b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/SpringCacheCustomizer.java new file mode 100644 index 0000000000..5c1bba922b --- /dev/null +++ b/spring-caching-2/ttl/src/main/java/com/baeldung/caching/ttl/service/SpringCacheCustomizer.java @@ -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 { + + @Override + public void customize(ConcurrentMapCacheManager cacheManager) { + cacheManager.setCacheNames(asList("hotels")); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/resources/application.yml b/spring-caching-2/ttl/src/main/resources/application.yml new file mode 100644 index 0000000000..8f45cc140d --- /dev/null +++ b/spring-caching-2/ttl/src/main/resources/application.yml @@ -0,0 +1,15 @@ +spring: + jpa: + open-in-view: true + hibernate: + ddl-auto: create-drop + show-sql: false + +server: + port: 8000 + +caching: + spring: + hotelListTTL: 43200 + guava: + hotelItemTTL: 43200 \ No newline at end of file diff --git a/spring-caching-2/ttl/src/main/resources/data.sql b/spring-caching-2/ttl/src/main/resources/data.sql new file mode 100644 index 0000000000..6b2250be00 --- /dev/null +++ b/spring-caching-2/ttl/src/main/resources/data.sql @@ -0,0 +1,36 @@ +SET REFERENTIAL_INTEGRITY FALSE; +TRUNCATE TABLE city; +TRUNCATE TABLE hotel; +SET REFERENTIAL_INTEGRITY TRUE; +ALTER TABLE city + ALTER COLUMN id RESTART WITH 1; +ALTER TABLE hotel + ALTER COLUMN id RESTART WITH 1; + +INSERT INTO city(id, name, city_centre_latitude, city_centre_longitude) +VALUES (1, 'Amsterdam', 52.368780, 4.903303); +INSERT INTO city(id, name, city_centre_latitude, city_centre_longitude) +VALUES (2, 'Manchester', 53.481062, -2.237706); + +INSERT INTO hotel(name, deleted, rating, city_id, address, latitude, longitude) +VALUES ('Monaghan Hotel', false, 9.2, 1, 'Weesperbuurt en Plantage', 52.364799, 4.908971); +INSERT INTO hotel(name, deleted, rating, city_id, address, latitude, longitude) +VALUES ('The Thornton Council Hotel', false, 6.3, 1, 'Waterlooplein', 52.3681563, 4.9010029); +INSERT INTO hotel(name, deleted, rating, city_id, address, latitude, 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, latitude, 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, latitude, longitude) +VALUES ('Fitting Image Hotel', false, NULL, 1, 'Staatsliedenbuurt', 52.380936, 4.8708297); +INSERT INTO hotel(name, deleted, rating, city_id, address, latitude, 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, latitude, longitude) +VALUES ('201 Deansgate Hotel', false, 7.3, 2, '201 Deansgate', 53.4788305, -2.2484721); +INSERT INTO hotel(name, deleted, rating, city_id, address, latitude, 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, latitude, longitude) +VALUES ('Sunlight House', false, 4.3, 2, 'Little Quay St', 53.4785129, -2.2505943); +INSERT INTO hotel(name, deleted, rating, city_id, address, latitude, 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, latitude, longitude) +VALUES ('Marriot Bonvoy', false, 9.6, 1, 'Hans Zimmerstraat', 53.477872, -2.2462003); \ No newline at end of file diff --git a/spring-caching-2/ttl/src/test/java/com/baeldung/caching/ttl/controller/HotelControllerIntegrationTest.java b/spring-caching-2/ttl/src/test/java/com/baeldung/caching/ttl/controller/HotelControllerIntegrationTest.java new file mode 100644 index 0000000000..821e7341c8 --- /dev/null +++ b/spring-caching-2/ttl/src/test/java/com/baeldung/caching/ttl/controller/HotelControllerIntegrationTest.java @@ -0,0 +1,71 @@ +package com.baeldung.caching.ttl.controller; + +import com.baeldung.caching.ttl.model.Hotel; +import com.baeldung.caching.ttl.repository.CityRepository; +import com.baeldung.caching.ttl.repository.HotelRepository; +import com.booking.testing.SlowTest; +import com.fasterxml.jackson.databind.ObjectMapper; +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.http.MediaType; +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.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +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 ObjectMapper mapper; + + @Autowired private HotelRepository repository; + @Autowired private CityRepository cityRepository; + + @Test + @DisplayName("When all hotels are requested then they are all returned") + void whenAllHotelsRequested_thenReturnAllHotels() throws Exception { + mockMvc + .perform(get("/hotel")) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$", hasSize((int) repository.findAll().stream().count()))); + } + + @Test + @DisplayName("When a hotel is requested by id then the hotel is returned") + void whenAGivenHotelsRequested_thenReturnTheHotel() throws Exception { + Long hotelId = 1L; + Hotel hotel = + mapper + .readValue( + mockMvc + .perform( + get("/hotel/" + hotelId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(), + Hotel.class); + + assertThat( + repository + .findById(hotelId) + .orElseThrow(() -> new IllegalStateException(String + .format("Hotel with id %s does not exist even in repository", hotelId))), + equalTo(hotel)); + } +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/test/java/com/booking/testing/SlowTest.java b/spring-caching-2/ttl/src/test/java/com/booking/testing/SlowTest.java new file mode 100644 index 0000000000..20a9ceb8ec --- /dev/null +++ b/spring-caching-2/ttl/src/test/java/com/booking/testing/SlowTest.java @@ -0,0 +1,4 @@ +package com.booking.testing; + +public @interface SlowTest { +} \ No newline at end of file diff --git a/spring-caching-2/ttl/src/test/resources/application.yml b/spring-caching-2/ttl/src/test/resources/application.yml new file mode 100644 index 0000000000..a7836fc5da --- /dev/null +++ b/spring-caching-2/ttl/src/test/resources/application.yml @@ -0,0 +1,15 @@ +server: + port: 8001 + +spring: + jpa: + open-in-view: true + hibernate: + ddl-auto: create-drop + show-sql: false + +caching: + spring: + hotelListTTL: 43200 + guava: + hotelItemTTL: 43200 \ No newline at end of file