Spring Cache – wstęp i praktyczne wykorzystanie możliwości

Poniższym artykułem chciałbym przybliżyć mechanizm działania cache oraz przedstawić praktyczne wykorzystanie i konfigurację Spring Cache. Zaczynając od podstaw, krok po kroku przejdziemy przez dodawanie adnotacji i ustawianie parametrów, tym samym pokazując sposoby konfiguracji oraz możliwości wykorzystania cache’owania danych, oferowane przez framework Spring. Co więcej, szerzej poruszymy wątek użycia i konfiguracji zewnętrznych providerów (EhCache, Redis). Wszystko po to, aby na końcu nasza aplikacja działała szybko i wydajnie.

Spring Cache - wstęp i praktyczne wykorzystanie możliwości

Co to jest cache?

Cache, czyli pamięć podręczna, to mechanizm pozwalający przechowywać w pamięci dane, które już raz zostały użyte. Celem jest zwiększenie szybkości dostępu do informacji, w przypadku gdy będą one nam potencjalnie potrzebne w najbliższej przyszłości.

Mechanizm ten pozwala ograniczyć ilość zapytań do bazy danych, czy też liczbę wywołań zewnętrznych serwisów, z których korzystamy.

Cache jest wykorzystywany praktycznie we wszystkich systemach. Zaczynając od sprzętu komputerowego, możemy znaleźć go w procesorach, w pamięci RAM, a także w najróżniejszych aplikacjach desktopowych, mobilnych czy internetowych.

Tworząc aplikacje mamy możliwość użycia wielu cache’y, które głównie skupiają się na cache’owaniu danych z bazy danych, a ich głównym zadaniem jest optymalizacja zapytań i przyspieszenie działania naszej aplikacji. Ja natomiast chciałbym się skupić na możliwości wykorzystania cache’a, którego udostępnia nam framework Spring, czyli Spring Cache.

Co to jest Spring Cache?

Spring pozwala wprowadzić nam abstrakcję cache’a, czyli udostępnia API, dzięki któremu możemy korzystać z cache’a. Pozwala ona skonfigurować cache’a w elastyczny sposób poprzez dodanie odpowiednich zależności oraz adnotacji. Co więcej, jeśli chcemy korzystać z zewnętrznego providera, również wystarczy dołożyć odpowiednie zależności i konfigurację.

To, co charakteryzuje omawianą przeze mnie bibliotekę to możliwość korzystania z niej na poziomie metod w naszej aplikacji przy użyciu adnotacji. Twórcy Springa zadbali również o to, aby cache z którego możemy korzystać, mógł być wykorzystywany w aplikacjach wielowątkowych.

W Spring Cache możemy korzystać z jednego CacheManagera (interfejsu do zarządzania cache’em), ale także mamy możliwość korzystania z kilku na raz. Mamy możliwość dodania konfiguracji przechowywania danych czy czasu życia cache’a.

Wspierane przez Springa CacheManagery możemy podzielić na:

korzystające z wewnętrznych mechanizmów Springa np.: ConcurrentMapCacheManager, SimpleCacheManager.
umożliwiające konfigurację zewnętrznych providerów np.: CaffeineCacheManager, EhCacheCacheManager, JCacheCacheManager, RedisCacheManager.

Jak skonfigurować oraz używać Spring Cache?

Konfiguracja Spring Cache oraz korzystanie z niego jest łatwe i intuicyjne, więc przejdźmy teraz do tego zagadnienia i sami się o tym przekonajmy. Moja przykładowa aplikacja korzysta ze Spring Boota oraz z Mavena, dlatego konfigurację rozpoczynam od dodania odpowiednich zależności w pliku pom.xml.

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-cache</artifactId> 
</dependency> 

W przypadku gdy nasza aplikacja korzysta z samego Springa powinniśmy użyć:

<dependency> 
    <groupId>org.springframework</groupId> 
    <artifactId>spring-context</artifactId> 
    <version>5.1.8.RELEASE</version> 
</dependency> 

Tak jak wspomniałem wyżej, konfiguracja i używanie Spring Cache opiera się na adnotacjach. Oczywiście możemy również korzystać ze starego sposobu, czyli konfiguracji w pliku XML, ale według mnie korzystanie z adnotacji jest bardziej intuicyjne i łatwiejsze w zrozumieniu, niż dokładanie kolejnych linii w xml.

Adnotacja @Cacheable

Pierwszą adnotacją, z której powinniśmy skorzystać jest @EnableCaching w naszej klasie konfiguracyjnej:

@EnableCaching 
public class CacheConfig {

    @Bean 
    public CacheManager cacheManager() { 
    SimpleCacheManager cacheManager = new SimpleCacheManager(); 
    Cache postsCache = new ConcurrentMapCache("posts"); 
    cacheManager.setCaches(Arrays.asList(postsCache)); 
    return cacheManager; 
    }  
};  

Dzięki tej adnotacji możemy przejść do cache’owania danych. W Spring Cache odbywa się zazwyczaj w warstwie serwisów aplikacyjnych, ale zdarza się również, że cache jest dodawany na poziomie warstwy dostępu do danych (np. jeśli kilka serwisów korzysta z tych samych metod).

Naszą przygodę z dodaniem cache’a na poszczególnych metodach zaczynamy od adnotacji @Cacheable. Adnotacja ta wykorzystywana jest na metodach, które pobierają dane, ale nie modyfikują ich.

Posiada ona również parametry, które możemy, a nawet musimy ustawić.

@Cacheable(cacheNames = "PostsWithComments", key = "#page") 
public List<Post> getPostsWithComments(int page,Sort.Direction sort){ 
        List<Post> allPosts = postRepository.findAllPosts( 
        PareRequest.of(page, PAGE_SIZE, Sort.by(sort, "id") 
        )); 
        List<Long> ids=allPosts.stream() 
        .map(Post::getId) 
        .collect(Collectors.toList()); 
        List<Comment> comments =commentRepository.findAllByPostIdIn(ids); 
        allPosts.forEach( 
        post->post.setComment(extractComments(comments,post.getId()))); 
        return allPosts; 


@Cacheable(cacheNames = "SinglePost") 
public Post getSinglePost(long id) { 
    return postRepository.findById(id).orElseThrow(); 

Parametr cacheName

Parametrem, który należy ustawić jest cacheName. Pozwala on nadać nam nazwę naszego cache’a, co może być przydatne w konfiguracji np. zewnętrznego providera lub konfiguracji kilku CacheManager’ów.

Parametr key

Kolejny parametr to key, czyli klucz dodawany do naszego cache’a. Domyślnym generatorem kluczy w cache’u jest SimpleKeyGenerator, gdzie kluczem są wszystkie parametry w danej metodzie, czasami nie potrzebujemy ustalać go na podstawie wszystkich parametrów, dlatego możemy zdefiniować konkretnie jeden, używając do tego specjalnej składni, jaką jest Spring Expression Language (SpEL) tak jak w tym przykładzie key = ”#page’’.

Jako klucza możemy użyć również własnej metody, która jest zdefiniowana wewnątrz klasy np. hashCode().

Parametr keyGenerator

Wśród parametrów, które dostarcza nam @Cacheable są również keyGenerator gdzie możemy zdefiniować własny generator kluczy, cacheManager, condition, którym możemy określić przy pomocy SpEL kiedy cache ma przechowywać dane (np. condition = “#name.lenght < 32”) i kilka innych dodatkowych opcji.

Jeśli chodzi o keyGenerator, to Spring Cache korzysta z domyślnego keyGenerator’a, co czasami może stwarzać pewne problemy, gdy np. dwie metody korzystają z tego samego cache’a i posiadają te same parametry, które są domyślnie brane jako klucz.

Wtedy może dojść do kolizji lub dane w cache’u będą nadpisywane przez inne metody. Dlatego dobrą praktyką jest definiowanie klucza, lub dodanie własnego keyGeneratora.

Aby tego dokonać, musimy stworzyć własną klasę, która będzie nadpisywać domyślny generator kluczy. Musi ona implementować interfejs KeyGenerator, który udostępnia nam metodę generate(). To właśnie w niej możemy zdefiniować, jak mają się nadawać nasze klucze.

public class CustomKeyGenerator implements KeyGenerator {

    @Override 
    public Object generate(Object target, Method method, Object... params) { 
    return target.getClass().getSimpleName() + "_" 
    + method.getName() + "_" 
    + StringUtils.arrayToDelimitedString(params, "_"); 
    } 
}

Dzięki temu mamy dwie możliwości użycia naszego generatora, jedną z nich jest zadeklarowanie beana w naszej klasie konfiguracyjnej:

@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    @Bean("customKeyGenerator") 
    public KeyGenerator keyGenerator() { 
    return new CustomKeyGenerator(); 
    } 

Należy zwrócić uwagę, że klasa konfiguracyjna dziedziczy po klasie CachingConfigurerSupport, ponieważ pozwala nam to na utworzenie beana właśnie dla keyGenerator, ale także możemy stworzyć go dla cacheManager czy cacheResolver.

Natomiast drugim sposobem użycia jest dodanie odpowiednich parametrów na metodzie, która korzysta z adnotacji @Cacheable:

@Override 
@Cacheable(value = "authors", keyGenerator = "customKeyGenerator") 
public List<AuthorDto> getAll() { 
    return authorRepository.findAll().stream() 
            .map(AuthorMapper::toDto
            .collect(Collectors.toList()); 

Dzięki własnemu keyGeneratorowi możemy korzystać zarówno z domyślnego generatora kluczy jak i z własnego. Każda z metod może korzystać z innych generatorów kluczy, więc nie jesteśmy ograniczeni tylko do jednego z nich. Odbywa się to na zasadzie keyGenerator per metoda.

Jak działa Spring Cache?

Może teraz przejdźmy do kilku słów o tym, jak w ogóle działa Spring Cache. Mechanizm Spring Cache działa w oparciu o aspekty (Aspect Oriented Programming). Mamy tutaj aspekt, który odpowiada za tworzenie cache’a.

Reaguje on na adnotację, w tym przypadku na @Cacheable na danej metodzie i tworzy on cache dla niej. Za całą tą czynność odpowiada klasa CacheInterceptor. Rozszerza ona klasę CacheAspectSupport oraz implementuje MethodInterceptor.

Cały proces i działanie tego mechanizmu nie są proste do wyjaśnienia, ale najważniejszą rzeczą, którą należy wiedzieć, jest to, że klasa CacheInterceptor po prostu wywołuje odpowiednie metody nadklas w odpowiedniej kolejności.

Jeśli ktoś jest zainteresowany dokładnymi szczegółami tego mechanizmu zachęcam do przeczytania dokumentacji Springa – myślę, że jest to najlepsze źródło informacji.

Wspomniałem już o tym, że domyślnym cache managerem w Spring Cache jest ConcurrentMapCacheManager. To właśnie ten manager zapisuje cache w ConcurrentHashMap i dla każdego naszego cache’a używana jest klasa ConcurrentMapCache. Klasa ta również zapisuje dane w ConcurrentHashMap, a w polu store zapisywane są poszczególne wpisy dla naszego cache’a.

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware { 
    private final ConcurrentMap<String, Cache> cacheMap =  
                  new ConcurrentHashMap(16); 
    private boolean dynamic = true; 
    private boolean allowNullValues = true; 
    private boolean storeByValue = false; 
    @Nullable 
    private SerializationDelegate serialization;
 
    public ConcurrentMapCacheManager() { 
    }

public class ConcurrentMapCache extends AbstractValueAdaptingCache { 
    private final String name; 
    private final ConcurrentMap<Object, Object> store; 
    @Nullable 
    private final SerializationDelegate serialization; 
 
    public ConcurrentMapCache(String name) { 
    this(name, new ConcurrentHashMap(256), true); 
    } 
 
    public ConcurrentMapCache(String name, boolean allowNullValues) { 
    this(name, new ConcurrentHashMap(256), allowNullValues); 
    }   

Zasadniczo dzięki temu, nasz cache działa. Niestety, domyślny cache manager nie daje nam możliwości zarządzania naszym cache’em: nie możemy wyczyścić danego cache’a po określonym czasie, nie możemy ustalić limitu danych, które mają być zapisywane, więc wszystkie operacje do zarządzania cache’em są po naszej stronie, do wykonywania „ręcznie”.

W tym przypadku pomocne mogą się okazać dodatkowe adnotacje, które udostępnia nam Spring Cache.

Adnotacja @CachePut

Warto wspomnieć o adnotacji @CachePut. Adnotacja dodawana jest na metodach, które modyfikują dane. W sytuacji, gdy metoda z tą adnotacją jest wywoływana, to aktualizuje ona dany wpis w cache’u, dzięki czemu zawsze mamy najświeższe zmiany, z których możemy skorzystać.

Ważne jest tutaj dodanie parametrów jak w adnotacji @Cacheable. Czyli musimy ustawić cacheName oraz key.

W tym przykładzie chcemy, żeby naszym kluczem było id, dlatego musimy je wyciągnąć z result (jest to specyficzna nazwa w SpEL’u i jest to obiekt, który zwraca nam dana metoda, czyli postEdited = result):

@Transactional 
@CachePut(cacheNames = "SinglePost", key = "#result.id") 
public Post editPost(Post post) { 
   Post postEdited = postRepository.findById(post.getId()).orElseThrow(); 
   postEdited.setTitle(post.getTitle()); 
   postEdited.setContent(post.getContent()); 
   return postEdited; 

Adnotacja @CacheEvict

Ostatnią adnotacją, o której chciałbym tutaj wspomnieć jest adnotacja @CacheEvict, która pozwala nam na czyszczenie cache’a. Dodajemy ją na metodach, które usuwają dane.

Dzięki temu w przypadku wywołania metody usuwającej jakieś dane, nasz cache również będzie czyszczony z tego wpisu. Pozwala to zaoszczędzić miejsce w pamięci cache’a i jest go trudniej przepełnić, ponieważ nie posiada on niepotrzebnych danych, ale także pozwala zachować spójność pomiędzy głównym źródłem danych, a cache’em.

Tradycyjnie obowiązkowe jest dodanie cacheName oraz key, ale w tym przypadku key możemy pominąć, ponieważ kluczem będzie id, które jest w parametrze metody:

@CacheEvict(cacheNames = "SinglePost") 
public void deletePost(long id) { 
    postRepository.deleteById(id); 

Jak widzimy, domyślny cache manager pozwala nam kontrolować cache w minimalnym stopniu, ale wystarczającym do poprawnego jego działania.

Zewnętrzni providerzy

Dzięki zewnętrznym provider’om możemy nadać większą elastyczność cache’om np. zdefiniować ilości danych jaka może się zapisywać w cache’u czy też ustalić, po jakim czasie nasz cache powinien się czyścić. Dodatkowym plusem zewnętrznych provider’ów jest to, że pozwalają one na dłuższe życie cache’a niż samej aplikacji.

Taką możliwość daje nam np. Redis (o którym więcej informacji będzie niżej w tym artykule), który działa osobno niezależnie od aplikacji.

Nasza aplikacja może zostać zrestartowana, a dane w cache’u będą nadal przechowywane, podczas gdy ConcurrentHashMap zostaje wyczyszczone po restarcie aplikacji. W Springu możemy skorzystać z provider’ów które są określone w klasie CacheConfiguration:

final class CacheConfigurations { 
    private static final Map<CacheType, Class<?>> MAPPINGS
 
    private CacheConfigurations() { 
    } 
    . 
    . 
    . 

static { 
Map<CacheType, Class<?>> mappings = new EnumMap(CacheType.class); 
    mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class); 
    mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class); 
    mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); 
    mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); 
    mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); 
    mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); 
    mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); 
    mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); 
    mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); 
    mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); 
    MAPPINGS = Collections.unmodifiableMap(mappings); 

Skoro domyślny cache manager nie pozwala nam na swobodną konfigurację naszego cache’a, użyjmy zewnętrznego providera i skonfigurujmy go tak, aby spełniał nasze potrzeby, a także wykorzystajmy jego możliwości w celu łatwiejszego i lepszego sterowania przechowywanymi danymi.

Jak dodać zewnętrzny provider?

W tym przykładzie użyjemy EhCache, który daje nam trzy możliwości zapisywania danych:

– MemoryStore – czyli zapisywanie na heapie, tzn. że zapisujemy dane w stercie JVM, ale minusem tutaj jest to, że dane te obsługiwane są przez Garbage Collector

– OffHeapStore – czyli zapisywanie poza heapem, tutaj już Garbage Collector nie działa, ale też zapisywanie danych jest wolniejsze niż w przypadku zapisywania danych na heapie.

– DiskStore – czyli zapisywanie danych na dysku.

Dodatkowo EhCache korzysta z algorytmu Least Recently Used (LRU) co oznacza, że najstarsze elementy, najrzadziej używane są usuwane pierwsze z cache’a jeśli zostanie on przepełniony.

Konfiguracja EhCache

W takim razie przejdźmy do konfiguracji i podstawowych możliwości użycia go. Pierwszą rzeczą, którą musimy wykonać jest dodanie zależności w naszym pliku pom.xml:

<dependency> 
    <groupId>org.ehcache</groupId> 
    <artifactId>ehcache</artifactId> 
    <version>3.5.3</version> 
</dependency> 
<dependency> 
    <groupId>javax.cache</groupId> 
    <artifactId>cache-api</artifactId> 
    <version>1.1.1</version> 
</dependency> 

Oraz od dodania nowego properties’a w pliku application.properties:

spring.cache.jcache.config=classpath:ehcache.xml

Properties ten wskazuje nam plik konfiguracyjny dla EhCache’a, który w kolejnym kroku musimy dodać. W tym przykładzie plik nazywa się ehcache.xml i wygląda następująco:

<config xmlns="http://www.ehcache.org/v3" 
        xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:jsr107 ="http://www.ehcache.org/v3/jsr107" 
        xsi:schemaLocation ="http://www.ehcache.org/v3  
https://www.ehcache.org/schema/ehcache-core-3.0.xsd">

<cache-template name="default"> 
        <expiry> 
            <ttl unit="hours">1</ttl> 
        </expiry> 
        <heap unit="entries">1000</heap> 
    </cache-template> 
     
    <cache alias="PostsWithComments" uses-template="default"/> 
     
    <cache alias="SinglePost"> 
        <expiry> 
            <ttl unit="hours">2</ttl> 
        </expiry> 
        <heap unit="entries">500</heap> 
    </cache> 
</config> 

Cała konfiguracja naszego cache’a odbywa się teraz właśnie tutaj. EhCache pozwala nam na utworzenie konkretnego template’a, z którego będziemy mogli korzystać w kilku cache’ach.

W przykładzie został utworzony template o nazwie default, w którym jednym z ustawień jest sekcja expiry, w której możemy ustalić czas, po jakim nasz wpis ma być usunięty z cache’a (ttl) lub czas, po jakim nasz cache ma być czyszczony w przypadku braku odpytywania go (tti).

Kolejną rzeczą, jaką możemy ustawić jest heap, czyli ilość elementów, które mają być przechowywane w cache’u.

Dostępnych jest znacznie więcej opcji konfiguracji – zachęcam do przeczytania dokumentacji, w której możemy poznać dodatkowe opcje, ale w tym przykładzie chciałem pokazać tylko podstawowe funkcje wykorzystania tego providera.

Kolejnym etapem jest wywołanie naszego template na konkretnym cache’u, czyli <cache alias…, gdzie podajemy nazwę cache’a dodanego przez nas (dlatego nadanie nazwy w adnotacji @Cacheable jest konieczne) i użycie danego template (uses-template). Możemy oczywiście też skonfigurować jednego konkretnego cache’a, co zostało pokazane wyżej dla SinglePost.

Jeśli nie EhCache, to co innego?

Do tematu dodawania zewnętrznego providera warto też wspomnieć o Redis. Od jakiegoś czasu staje się standardem, który uznawany jest jako default, jeżeli chodzi o bazę klucz – wartość do cache’owania danych. Jest on magazynem danych w pamięci, używanym jako baza danych i pamięć podręczna.

Wykorzystanie Redis

Żeby móc korzystać z Redis, możemy uruchomić go z wykorzystaniem obrazu Dockerowego lub zainstalować go bezpośrednio na naszym systemie. Na potrzeby tego artykułu zakładamy, że mamy to już zrobione i możemy skupić się tylko na wykorzystaniu cache’a.

Przed rozpoczęciem wykorzystania Redisa jako pamięć podręczną należy wspomnieć o podstawowych parametrach konfiguracyjnych, takich jak maksymalna ilość pamięci, algorytmy czyszczenia i trwałość.

1. Maksymalna ilość pamięci – domyślnie Redis nie ma limitów pamięci w systemach 64-bitowych, natomiast w systemach 32-bitowych maksymalna pamięć wynosi 3 GB.

2. Algorytmy czyszczenia – gdy rozmiar pamięci podręcznej osiąga swój limit, stare dane są usuwane, aby zrobić miejsce na nowe. Tutaj wyróżniamy dwie strategię usuwania starych danych:
 – Least Recently Used (LRU) – gdy dojdzie do przepełnienia pamięci klucze, które były używane jako ostatnie (najstarsze), zostaną usunięte.
– Least Frequently Used (LFU) – Redis policzy, ile razy dany klucz był używany i na podstawie tego, klucze z większą ilością użyć zostaną dłużej przechowane niż klucze, które były użyte np. Raz.

3. Trwałość – na starcie cache jest zawsze pusty, czasami z niezależnych przyczyn możemy utracić połączenie, lub doświadczyć restart Redis,u dlatego warto korzystać ze snapshotów, które pomogą nam w odzyskaniu danych w cache’u.

Konfiguracja Cache’a w Redis

Redis posiada dwa typy wykonywania snapshotów:


– RDB – wykonuje migawki w interwałach i w konfiguracji to my określamy, co ile (np. sekund), ma się wykonać zapis oraz gdy zmieni się ustalona przez nas liczba kluczy. Zapis ten odbywa się do pliku w formacie .rdb. W przypadku awarii lub wyłączenia Redisa, po ponownym uruchomieniu możemy posiadać dane ostatnio zapisane do pliku, które niekoniecznie mogą odzwierciedlać stan zaraz sprzed restartu.
– AOF – wykonuje migawkę po każdej operacji zapisu odebraną przez serwer. Dzięki temu dane są zawsze aktualne, a w przypadku uruchomienia od nowa Redisa, przywraca on stan cache’a do stanu sprzed awarii/wyłączenia.

Zachęcam do przeczytania dokumentacji Redisa, w której uwzględniono plusy i minusy każdego z tych typów snapshotów.

Konfiguracja samego cache’a w Redis polega na ustawieniu wyżej wspomnianych parametrów w pliku konfiguracyjnym redis.conf, który znajduje się po stronie Redis, a nie w naszym projekcie, tak jak było to w przypadku EhCache.

#memory limit up to 128MB
maxmemory 128mb
#remove the last recently used (LRU) keys first
maxmemory-policy allkeys-lru

Tutaj jako przykład dodałem konfigurację, gdzie maksymalna ilość przechowywanych danych nie może przekroczyć 128 MB, a dane z cache’a będą usuwane na podstawie strategii LRU.

Konfigurację rozpoczynamy oczywiście od dodania zależności do naszego pliku pom.xml:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Kolejnym krokiem jest dodanie odpowiednich properties’ów w pliku application.properties>:

spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379

Ustawienia te definiują nam lokalizację usług cache’a.

Samo korzystanie z tego cache’a jest identyczne jak w przypadku korzystania z domyślnego cacheManager’a w Spring Cache. Czyli najprościej mówiąc, wykorzystujemy wspomniane już wyżej w tym artykule adnotacje: @EnableCaching – w naszej klasie konfiguracyjnej, @Cacheable, @CachePut, @CacheEvict. Różnicą może być tutaj to, że w każdej adnotacji musi być zdefiniowany parametr key przy użyciu SpEL:

@Cacheable(value = "author", key = "#id")
@GetMapping(value = "/author/{id}")
public ResponseEntity<AuthorDto> get(@PathVariable long id) throws NotFoundException {
log.info("User try to get author with id: {}", id);
AuthorDto authorDto = authorService.get(id);
return new ResponseEntity<>(authorDto, HttpStatus.OK);
}

@CachePut(value = "author", key = "#id")
@PutMapping(value = "/author/{id}")
public ResponseEntity<AuthorDto> update(@Valid @RequestBody AuthorDto authorDto, @PathVariable long id) throws NotFoundException {
log.info("User try to update author with id: {}", id);
authorService.update(id, authorDto);
return new ResponseEntity<>(authorDto, HttpStatus.OK);
}

Redis daje nam również możliwość śledzenia różnych statystyk, takich jak:


hit/miss ratio – czyli stosunek trafionych zapytań o dany klucz, do nietrafionych
 latency – opóźnienie, czyli maksymalną długość pomiędzy żądaniem, a odpowiedzią
evicted keys – czyli usunięte klucze po przepełnieniu maksymalnego rozmiaru pamięci

Podsumowanie

Jak widać, Spring Cache stanowi niezwykle przydatne narzędzie dla programistów, chcących efektywnie zarządzać dostępem do danych w aplikacjach opartych na Springu.

Zastosowanie tego mechanizmu w odpowiednich warstwach aplikacji może znacząco przyspieszyć operacje odczytu danych, jednocześnie redukując zapytania do baz danych. Ostatecznie przekłada się to na wydajność i szybkość działania oprogramowania, co jest szczególnie ważne, jeśli w grę wchodzi szybki i intensywny rozwój aplikacji.

Warto podkreślić, że elastyczność konfiguracji Spring Cache oraz możliwość integracji z różnymi providerami czynią go wszechstronnym narzędziem dla developerów, dążących do optymalizacji i wydajności swoich systemów.