Cachowanie w Javie z użyciem Springa
Alexander Stasiak
10 lut 2026・12 min czytania
Spis treści
Zrozumienie cache’owania w aplikacjach Java
Pierwsze kroki: włączanie cache’owania w projekcie Spring Boot
Korzystanie ze Spring Boot Starter Cache
Cache’owanie w Spring Core bez Spring Boot
Włączanie i konfigurowanie abstrakcji cache Springa
Definiowanie nazw cache i regionów
Korzystanie z adnotacji Spring do cache’owania
@Cacheable: cache’owanie wyników metod
@CacheEvict: usuwanie nieaktualnych wpisów
@CachePut: wymuszanie aktualizacji cache
@Caching: łączenie wielu operacji cache
@CacheConfig: centralizacja ustawień cache
Cache warunkowy i zaawansowane strategie kluczy
Użycie atrybutu condition
Użycie atrybutu unless
Integracja konfiguracji cache w Javie
Konfiguracja cache in-memory (np. Caffeine)
Konfiguracja cache rozproszonego (np. Redis)
Najlepsze praktyki, pułapki i podsumowanie
Jeśli Twoja aplikacja Spring Boot wielokrotnie pobiera te same dane z bazy lub zewnętrznego API, prawdopodobnie tracisz milisekundy — a nawet sekundy — przy każdym żądaniu. Cache w Javie ze Springiem rozwiązuje ten problem, przechowując często używane wyniki w szybkim magazynie, dzięki czemu kolejne wywołania pomijają kosztowną operację.
Abstrakcja cache w Springu, wprowadzona w Spring Framework 3.1 około 2011 r. i znacząco dopracowana w Spring Boot 1.0 w 2014 r., oferuje deklaratywne, oparte na adnotacjach podejście do cache’owania. Jej zaletą jest oddzielenie logiki biznesowej od dostawcy cache. Niezależnie od tego, czy używasz prostego in-memory cache na etapie developmentu, czy Redis w produkcji, kod Twoich serwisów pozostaje niezmieniony.
Ten przewodnik przeprowadzi Cię przez włączanie, konfigurację i użycie cache’owania w nowoczesnym projekcie Spring Boot 3 / Java 17. Oto, co zyskasz:
- Lepsza wydajność: Skróć czasy odpowiedzi z setek milisekund do poniżej 1 ms dla operacji trafiających w cache
- Prostsze zarządzanie cache: Dodawaj cache adnotacjami zamiast pisać szablonowy kod
- Łatwiejsza zmiana dostawcy: Przełączaj się między ConcurrentMap, Caffeine, Redis czy Ehcache bez dotykania logiki biznesowej
- Wzorce gotowe na produkcję: Poznaj cache warunkowy, usuwanie wpisów i strategie multi-cache
Zrozumienie cache’owania w aplikacjach Java
Cache przechowuje często używane dane w szybkim magazynie — zwykle w RAM — aby uniknąć powtarzania kosztownych operacji. W backendach Java oznacza to przechwytywanie wywołań metod i zwracanie wcześniej obliczonych wyników zamiast wielokrotnego wykonywania tej samej logiki.
Konkretnie, cache błyszczy w takich scenariuszach:
- Szczegóły produktu pobierane przez JPA: Serwis e-commerce wywołuje productRepository.findById(productId) tysiące razy na godzinę dla popularnych pozycji
- Dane profilu użytkownika z zewnętrznego REST API: Każde pobranie trwa 150–300 ms przez opóźnienia sieci
- Wartości konfiguracji z usługi zdalnej: Feature flagi i ustawienia, które rzadko się zmieniają, ale są pobierane przy każdym ładowaniu strony
Wpływ na wydajność jest znaczący. Wywołanie bazy zwykle trwa 200–300 ms, biorąc pod uwagę narzut połączenia, wykonanie zapytania i mapowanie wyników. Odczyt z cache kończy się poniżej 1 ms. Dla endpointu o dużym ruchu, obsługującego 10 000 żądań na godzinę, ta różnica kumuluje się w godziny zaoszczędzonego czasu obliczeniowego dziennie.
Cache adresuje kilka typowych problemów:
- Wysokie opóźnienia przy powtarzanych odczytach tych samych danych
- Wąskie gardła bazy pod obciążeniem, gdy wiele żądań wykonuje identyczne zapytania
- Throttling zewnętrznych API z limitami zapytań
- Zbędne cykle obliczeniowe dla deterministycznych kalkulacji
Abstrakcja cache Springa celuje konkretnie w wyniki metod. To coś innego niż HTTP caching lub caching w CDN. Tamte warstwy cachują odpowiedzi bliżej klienta, podczas gdy cache Spring działa w warstwie serwisowej Twojej aplikacji webowej.
Pierwsze kroki: włączanie cache’owania w projekcie Spring Boot
Ta sekcja pokazuje, jak przejść od „czystej” aplikacji Spring Boot 3 — utworzonej Spring Initializr w 2025 r. — do wersji z podstawowym cache’owaniem. Całość zajmuje kilka minut.
Upewnij się, że projekt używa:
- Java 17 lub nowszej
- Spring Boot 3.x
- Maven (pom.xml) lub Gradle (build.gradle.kts) jako narzędzia budowania
Musisz dodać zależność spring-boot-starter-cache. Ten starter dostarcza moduł spring-context-support i wszystko, co potrzebne do infrastruktury cache. Do prostego demo domyślny ConcurrentMapCacheManager działa bez dodatkowych zależności.
Kluczowym krokiem jest dodanie @EnableCaching do głównej klasy aplikacji Spring Boot lub dedykowanej klasy konfiguracyjnej. Wygląda to tak:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class BookstoreApplication {
public static void main(String[] args) {
SpringApplication.run(BookstoreApplication.class, args);
}
}Z tymi dwoma elementami — zależnością i adnotacją — cache jest gotowy do użycia.
Korzystanie ze Spring Boot Starter Cache
Dodanie spring-boot-starter-cache aktywuje auto-konfigurację Spring Boot dla infrastruktury cache. To zalecane podejście dla większości projektów.
Dodaj zależność do pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>Dla Gradle dodaj implementation 'org.springframework.boot:spring-boot-starter-cache' w sekcji dependencies.
Co robi auto-konfiguracja:
- Rejestruje domyślny menedżer cache na podstawie tego, co jest na classpath
- Skanuje adnotacje cache po wykryciu @EnableCaching
- Podpina cache na podstawie właściwości w application.yml lub application.properties
- Dostarcza sensowne domyślne ustawienia działające „od ręki”
Ten starter to ten sam punkt wejścia niezależnie od tego, czy finalnym dostawcą cache jest Caffeine, Redis, czy prosta mapa w pamięci. Różnica dotyczy tylko dodatkowych zależności.
Cache’owanie w Spring Core bez Spring Boot
Cache działa też w „czystych” aplikacjach Spring — np. Spring Framework 6.x bez Boot. Wtedy ręcznie dodajesz zależności spring-context i spring-context-support.
Bez auto-konfiguracji Boot musisz jawnie zdefiniować bean CacheManager w klasie konfiguracyjnej:
- Utwórz klasę konfiguracji z adnotacją @EnableCaching
- Dodaj metodę @Bean zwracającą wybraną implementację CacheManager
- Przykładowo, zwróć new ConcurrentMapCacheManager("books", "users") z predefiniowanymi nazwami cache
- Opcjonalnie skonfiguruj bardziej zaawansowany menedżer, np. CaffeineCacheManager
To podejście jest spotykane w starszych wdrożeniach Java EE, samodzielnych bibliotekach opartych na Spring lub tam, gdzie „opinie” Spring Boot nie pasują do Twoich wymagań.
Włączanie i konfigurowanie abstrakcji cache Springa
Po dodaniu zależności kolejnym krokiem jest włączenie zachowania cache i zdefiniowanie, jak działa Twój CacheManager.
Adnotacja @EnableCaching uruchamia post-processor, który skanuje beany Springa w poszukiwaniu adnotacji cache i tworzy wokół nich proxy. Proxy przechwytują wywołania metod, aby sprawdzić, czy wynik jest już w cache, zanim wykonają metodę.
Spring Boot wybiera domyślną konfigurację CacheManager na podstawie detekcji na classpath:
| Dostawca na classpath | Użyty CacheManager |
|---|---|
| Brak (tylko starter) | ConcurrentMapCacheManager |
| Caffeine | CaffeineCacheManager |
| Redis (spring-boot-starter-data-redis) | RedisCacheManager |
| EhCache | EhCacheCacheManager |
Na produkcji zalecana jest jawna konfiguracja. Zdefiniuj bean CaffeineCacheManager lub RedisCacheManager, aby kontrolować:
- Time To Live (TTL) wpisów
- Maksymalny rozmiar przed usuwaniem
- Polityki usuwania (LRU, LFU, oparte na rozmiarze)
- Konfiguracje per-cache z różnymi politykami
Spring udostępnia też CacheManagerCustomizer<T extends CacheManager> jako hak do doprecyzowania cache tworzonych przez menedżerów auto-konfigurowanych bez ich pełnej podmiany.
Definiowanie nazw cache i regionów
Cache w Springu są grupowane według logicznych nazw — ciągów znaków jak „products”, „users” czy „exchangeRates”. Te nazwy mapują się na regiony/przestrzenie u dostawcy cache.
Najlepsze praktyki nazewnictwa:
- Wybieraj opisowe nazwy odzwierciedlające dane (np. „productById” zamiast „cache1”)
- Trzymaj spójność nazw między adnotacjami a plikami konfiguracyjnymi
- Konsekwentnie używaj liczby pojedynczej lub mnogiej
- W większych systemach rozważ prefiksowanie nazwą serwisu („catalog-products”, „pricing-rates”)
Przykładowo, klasa ProductService może używać:
- cache „productById” dla pojedynczych wyszukiwań po ID
- „allProducts” dla endpointów listujących kolekcje
- „productSearch” dla wyników wyszukiwania z parametrami zapytania jako kluczami
Niektórzy dostawcy cache, jak Redis i Ehcache, pozwalają określać konfiguracje per region w pliku XML lub application.yml, definiując różne TTL i rozmiary dla każdej nazwy cache.
Korzystanie z adnotacji Spring do cache’owania
Adnotacje na poziomie metod to podstawowy sposób cachowania danych w serwisach. Framework udostępnia wszystkie potrzebne adnotacje:
| Adnotacja | Cel |
|---|---|
| @Cacheable | Cache’uj wyniki metod; pomijaj wykonanie przy trafieniu |
| @CachePut | Zawsze wykonuj metodę; aktualizuj cache wynikiem |
| @CacheEvict | Usuwaj wpisy z cache |
| @Caching | Łącz wiele adnotacji na jednej metodzie |
| @CacheConfig | Ustal wspólne ustawienia cache na poziomie klasy |
Te adnotacje umieszcza się na publicznych metodach beanów Springa — zazwyczaj klas oznaczonych @Service lub @Repository. Tworzone przez włączenie cache proxy przechwytują wywołania i stosują zachowanie cache.
Domyślnie parametry metody tworzą klucz cache. Dla metody getBook(String isbn) wartość ISBN staje się kluczem. Możesz to nadpisać przy użyciu Spring Expression Language (SpEL) dla większej kontroli.
Poniższy przykład pokazuje te adnotacje w praktyce na BookService.
@Cacheable: cache’owanie wyników metod
@Cacheable jest przeznaczone dla operacji odczytu, gdzie wartość zwrotna może być ponownie użyta. Pomyśl o findById, getDetails czy innych metodach zwracających te same dane dla tych samych parametrów.
@Service
public class BookService {
private final BookRepository bookRepository;
@Cacheable(cacheNames = "books", key = "#isbn")
public Book getBookByIsbn(String isbn) {
// Ciało metody wykonuje się tylko przy chybieniu w cache
simulateSlowService();
return bookRepository.findByIsbn(isbn);
}
private void simulateSlowService() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}Jak to działa:
- Pierwsze wywołanie z ISBN „978-0134685991”: chybienie → metoda się wykonuje → wynik trafia do cache „books”
- Drugie wywołanie z tym samym ISBN: trafienie → ciało metody jest pomijane → natychmiast zwracany jest wynik z cache
- Wywołanie z innym ISBN: chybienie dla nowego klucza → metoda znów się wykonuje
Powyższa adnotacja wyzwala odczyt z cache przed wykonaniem metody. Jeśli istnieje wpis dla klucza, kosztowna operacja (baza/obliczenia) jest w całości pomijana.
Kiedy używać @Cacheable:
- Dane katalogowe rzadko się zmieniające
- Ustawienia i preferencje użytkowników
- Dane referencyjne jak kraje, waluty czy kategorie
Kiedy unikać @Cacheable:
- Metody intensywnie zapisujące i modyfikujące stan
- Metody z efektami ubocznymi poza zwracaniem danych
- Dane czasu rzeczywistego, które muszą być zawsze świeże
@CacheEvict: usuwanie nieaktualnych wpisów
@CacheEvict unieważnia wpisy cache, gdy dane pod spodem się zmieniają. Używaj na metodach update/delete, aby nie serwować starych danych.
@CacheEvict(cacheNames = "books", key = "#isbn")
public void updateBookPrice(String isbn, BigDecimal price) {
bookRepository.updatePrice(isbn, price);
}Po wykonaniu tej metody cache usuwa wpis dla tego konkretnego ISBN. Kolejny odczyt uderzy w bazę i odświeży cache.
Dla operacji masowych — jak nocne synchronizacje/importy — wyczyść cały cache:
@CacheEvict(cacheNames = "books", allEntries = true)
public void refreshAllBooks() {
// Logika importu masowego
bookRepository.syncFromExternalCatalog();
}Opcja beforeInvocation kontroluje moment usuwania:
- beforeInvocation = false (domyślnie): Usuwanie po pomyślnym wykonaniu metody
- beforeInvocation = true: Usuwanie przed wykonaniem, zapewniając czyszczenie nawet, jeśli metoda rzuci wyjątek
Zawsze paruj operacje modyfikujące dane z odpowiednim usuwaniem z cache. Pominięcie tego to jedna z najczęstszych przyczyn błędów ze starymi danymi w aplikacjach z cache.
@CachePut: wymuszanie aktualizacji cache
@CachePut zawsze wykonuje metodę i aktualizuje cache wartością zwrotną. Idealne dla operacji zapisu, które powinny odświeżać cache bez pomijania logiki.
@CachePut(cacheNames = "books", key = "#result.isbn")
public Book saveBook(Book book) {
return bookRepository.save(book);
}Zwróć uwagę na klucz #result.isbn — odnosi się on do wartości zwrotnej, czyli zapisanego bytu z uzupełnionymi polami generowanymi.
Kluczowe różnice względem @Cacheable:
| Aspekt | @Cacheable | @CachePut |
|---|---|---|
| Wykonanie metody | Pomijane przy trafieniu | Zawsze wykonywane |
| Aktualizacja cache | Tylko przy chybieniu | Zawsze aktualizowany |
| Typowy przypadek | Operacje odczytu | Operacje zapisu |
Unikaj umieszczania @Cacheable i @CachePut na tej samej metodzie. Sprzeczne zachowania wprowadzają zamieszanie. Zamiast tego wyraźnie rozdziel metody odczytu i zapisu.
Używaj @CachePut, gdy chcesz:
- Utrzymać cache „rozgrzany” po zapisach/aktualizacjach
- Uniknąć kolejnego chybienia w cache tuż po zapisie nowych danych
- Mieć pewność, że cache odzwierciedla najnowszy stan po modyfikacjach
@Caching: łączenie wielu operacji cache
Java nie pozwala wielokrotnie deklarować tej samej adnotacji na jednej metodzie. @Caching rozwiązuje to, grupując wiele adnotacji razem.
@Caching(
evict = {
@CacheEvict(cacheNames = "books", key = "#book.isbn"),
@CacheEvict(cacheNames = "bestsellers", allEntries = true)
},
put = {
@CachePut(cacheNames = "books", key = "#result.isbn")
}
)
public Book updateBookAndRefreshCaches(Book book) {
return bookRepository.save(book);
}Ta metoda:
- Usuwa konkretną książkę z cache „books”
- Czyści cały cache „bestsellers” (bo rankingi mogły się zmienić)
- Wkłada zaktualizowaną książkę ponownie do „books” ze świeżymi danymi
Typowe scenariusze dla @Caching:
- Pojedyncza aktualizacja wpływa na wiele regionów cache
- Potrzebujesz wielu adnotacji tego samego typu (np. usuwanie z trzech cache)
- Złożone przepływy, gdzie usuwanie i odświeżenie występują razem
Mimo mocy, intensywne użycie @Caching może utrudnić czytelność metod. Zarezerwuj je dla złożonych, dobrze udokumentowanych przypadków i dodawaj komentarze wyjaśniające powód wielu operacji cache.
@CacheConfig: centralizacja ustawień cache
Adnotacja @CacheConfig na poziomie klasy definiuje współdzielone atrybuty, by nie powtarzać ich w każdej metodzie. Zmniejsza to duplikację w serwisach korzystających z tego samego cache w wielu metodach.
@Service
@CacheConfig(cacheNames = "books")
public class BookService {
@Cacheable(key = "#isbn")
public Book getBookByIsbn(String isbn) {
return bookRepository.findByIsbn(isbn);
}
@CacheEvict(key = "#isbn")
public void deleteBook(String isbn) {
bookRepository.deleteByIsbn(isbn);
}
@CachePut(key = "#result.isbn")
public Book saveBook(Book book) {
return bookRepository.save(book);
}
}Z @CacheConfig(cacheNames = "books") na klasie, metody określają tylko klucz. Nazwa cache jest dziedziczona.
Możesz też skonfigurować:
- keyGenerator: nazwę customowego generatora kluczy dla wszystkich metod
- cacheResolver: customowy resolver cache wybierający cache dynamicznie
- cacheManager: konkretny bean menedżera cache, jeśli masz wiele menedżerów dla różnych dostawców
@CacheConfig samo nie aktywuje cache’owania. Nadal potrzebujesz @EnableCaching na poziomie konfiguracji, a poszczególne metody nadal muszą mieć @Cacheable, @CachePut lub @CacheEvict.
Cache warunkowy i zaawansowane strategie kluczy
Nie każde wywołanie metody powinno trafiać do cache. Czasem ma to sens tylko dla określonych wejść lub gdy wynik spełnia konkretne kryteria. Spring dostarcza atrybuty warunkowe do precyzyjnego sterowania zachowaniem cache.
Wszystkie główne adnotacje cache obsługują dwa atrybuty oparte na SpEL:
| Atrybut | Ewaluowany | Cel |
|---|---|---|
| condition | Przed wykonaniem metody | Decyduje, czy w ogóle stosować logikę cache |
| unless | Po wykonaniu metody | Decyduje, czy wynik powinien trafić do cache |
To ważne m.in. w scenariuszach:
- Cache tylko kosztownych lookupów dla poprawnych, dobrze uformowanych ID
- Pomijanie cache dla anonimowych/testowych użytkowników
- Unikanie „zanieczyszczania” cache wynikami null lub stanami błędów
Projektowanie klucza też ma znaczenie. Domyślne klucze oparte na wszystkich parametrach działają w prostych przypadkach, ale przy złożonych metodach lepiej jawnie definiować klucze, by unikać kolizji i dobrze obsłużyć wartości null.
Użycie atrybutu condition
Atrybut condition jest ewaluowany przed wykonaniem metody i decyduje, czy w ogóle brać pod uwagę cache. Jeśli wynik to false, metoda wykona się bez żadnej interakcji z cache.
@Cacheable(
cacheNames = "books",
key = "#isbn",
condition = "#isbn != null and #isbn.length() == 13"
)
public Book getBookByIsbn(String isbn) {
return bookRepository.findByIsbn(isbn);
}To cachuje tylko poprawne, 13-cyfrowe ISBN. Błędne lub null całkowicie omijają cache — bez odczytu i bez zapisu.
Inny przykład oparty na statusie klienta:
@Cacheable(
cacheNames = "pricing",
key = "#productId",
condition = "#customer.status == 'PREMIUM'"
)
public PricingDetails getPremiumPricing(Long productId, Customer customer) {
return pricingService.calculatePremiumPrice(productId, customer);
}Atrybut condition działa także z @CacheEvict i @CachePut. Np. w systemie multi-tenant możesz usuwać wpisy tylko dla wybranych tenantów:
@CacheEvict(
cacheNames = "tenantData",
key = "#tenantId",
condition = "#tenantId != 'system'"
)
public void updateTenantData(String tenantId, TenantConfig config) {
// Logika aktualizacji
}Użycie atrybutu unless
Atrybut unless jest ewaluowany po wykonaniu metody i może inspekcjonować wartość zwrotną przez #result. Pozwala to np. na „cache tylko, jeśli wynik nie jest null lub spełnia kryterium rozmiaru”.
@Cacheable(
cacheNames = "books",
key = "#isbn",
unless = "#result == null"
)
public Book getBookByIsbn(String isbn) {
return bookRepository.findByIsbn(isbn);
}To zapobiega cachowaniu brakujących rekordów. Bez tego, wyszukiwanie nieistniejącego ISBN zapisałoby null, co powodowałoby zwracanie null nawet po dodaniu książki do bazy.
Inny przykład oparty na rozmiarze wyniku:
@Cacheable(
cacheNames = "searchResults",
key = "#query",
unless = "#result.size() > 1000 or #result.isEmpty()"
)
public List<Book> searchBooks(String query) {
return bookRepository.search(query);
}To unika cachowania:
- Pustych wyników (co może oznaczać, że dopasowań jeszcze nie ma)
- Zbyt dużych wyników, które zjadłyby za dużo pamięci cache
condition i unless można stosować równocześnie:
- condition: „Czy w ogóle próbować cache?” (przed wykonaniem)
- unless: „Czy ten konkretny wynik zapisać?” (po wykonaniu)
Integracja konfiguracji cache w Javie
Choć Spring Boot dużo auto-konfiguruje, definiowanie konfiguracji cache w kodzie daje pełną kontrolę nad politykami jak TTL, maksymalny rozmiar i usuwanie.
Typowy wzorzec to utworzenie klas konfiguracji z metodami @Bean zwracającymi skonfigurowane instancje menedżerów cache:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats());
return manager;
}
}W konfiguracji Java możesz definiować różne polityki per cache. Na przykład:
- cache „customers” z TTL 10 minut dla często zmieniających się danych
- „countries” z TTL 24 godziny dla danych referencyjnych
- „exchangeRates” z TTL 5 minut dla wyników zewnętrznego API
Menedżer cache łączy Twoje metody z adnotacjami z faktycznym magazynem. CustomerDataService z @Cacheable("customers") użyje ustawień zdefiniowanych dla tej nazwy cache.
Konfiguracja cache in-memory (np. Caffeine)
Caffeine to wydajny lokalny cache dla aplikacji Spring uruchamianych na pojedynczej JVM. To zalecany wybór, gdy nie potrzebujesz cache rozproszonego między wieloma instancjami.
Dodaj zależność Caffeine:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>Skonfiguruj CaffeineCacheManager z politykami:
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("products", "categories");
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.recordStats());
return manager;
}Ta konfiguracja:
- Tworzy nazwaną pamięć cache dla „products” i „categories”
- Limituje każdą do maks. 10 000 wpisów (zapobiega zjadaniu pamięci przez nieużywane dane)
- Wygasza wpisy 5 minut po zapisie
- Rejestruje statystyki trafień/chybień do monitoringu
Dla prostszej konfiguracji Spring Boot oferuje integrację przez application.yml:
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=10000,expireAfterWrite=5mCaffeine sprawdza się w scenariuszach:
- Wdrożenia single-node lub małe klastry
- Wymagania ultra-niskiej latencji (odczyty poniżej mikrosekundy)
- Obciążenia z przewagą odczytów, gdzie osiągalny jest hit rate 95%+
Konfiguracja cache rozproszonego (np. Redis)
Redis jest szeroko używany produkcyjnie jako rozproszony, in-memory data store. To domyślny wybór dla cache w mikroserwisach lub skalowanych wdrożeniach Spring Boot, gdzie wiele instancji musi współdzielić dane cache.
Dodaj zależność Spring Data Redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>Skonfiguruj połączenie w application.yml:
spring:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
cache:
type: redis
redis:
time-to-live: 600sSpring Boot auto-konfiguruje RedisCacheManager albo możesz zdefiniować własny bean dla konfiguracji per-cache:
@Bean
public RedisCacheManagerBuilderCustomizer cacheManagerCustomizer() {
return builder -> builder
.withCacheConfiguration("shortLived",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(1)))
.withCacheConfiguration("longLived",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(24)));
}Konkretnie, Redis sprawdza się przy:
- Tokenach uwierzytelniających współdzielonych między instancjami za load balancerem
- Danych sesyjnych w bezstanowych wdrożeniach
- Cache’owaniu odpowiedzi API na potrzeby całego klastra
- Feature flagach i konfiguracji spójnej między węzłami
Warto rozważyć kompromisy:
- Opóźnienie sieci dodaje 5–10 ms względem cache w procesie (Caffeine)
- Wymagana serializacja (zwykle JSON przez Jackson), co dokłada narzut
- Lepsza skalowalność i współdzielony stan między serwisami
- Redis obsługuje 100k+ operacji na sekundę na shard

Najlepsze praktyki, pułapki i podsumowanie
Masz już podstawy cache’owania w Spring Boot: włączanie abstrakcji cache, użycie @Cacheable/@CachePut/@CacheEvict i innych adnotacji, cache warunkowy oraz konfigurację dostawców, takich jak Caffeine i Redis.
Kluczowe najlepsze praktyki:
- Dobieraj odpowiednie TTL: Równoważ świeżość i wydajność. Zbyt krótki to częste chybienia, zbyt długi to ryzyko starych danych
- Unikaj cache danych wysoce zmiennych: Jeśli dane zmieniają się co sekundę, cache tylko skomplikuje sytuację bez korzyści
- Projektuj czytelne nazwy cache: Opisowe, spójne, odzwierciedlające domenę
- Paruj zapisy z unieważnieniami: Każda metoda modyfikująca dane powinna usuwać/aktualizować odpowiednie cache
- Monitoruj trafienia/chybienia: Użyj /actuator/caches z Spring Boot Actuator, aby śledzić efektywność
Typowe pułapki, których należy unikać:
| Pułapka | Problem | Rozwiązanie |
|---|---|---|
| Self-invocation | Wywołanie cachowanej metody w tej samej klasie omija proxy | Wywołuj przez wstrzyknięty bean lub refaktoruj do osobnych klas |
| Brak unieważnień | Zapisy zachodzą, ale cache serwuje stare dane | Dodaj @CacheEvict do wszystkich metod modyfikujących |
| Nieograniczone cache | Pamięć rośnie aż do OOM | Zawsze ustawiaj maximumSize lub limity wpisów |
| Cache’owanie nulli | Brakujące rekordy cachowane w nieskończoność | Użyj unless = "#result == null" |
| Nadmiarowe cache’owanie | Każda metoda dostaje @Cacheable | Cache’uj selektywnie tam, gdzie latencja naprawdę ma znaczenie |
Aby zobaczyć efekt w praktyce, spróbuj tego ćwiczenia:
- Utwórz prosty endpoint /api/books/{isbn}, który wywołuje „wolną” metodę serwisu
- Dodaj @Cacheable do tej metody
- Zmierz czasy odpowiedzi przed i po, używając Spring Boot Actuator metrics lub narzędzia takiego jak JMeter
- Obserwuj logi — zobaczysz wykonanie metody tylko przy chybieniach
Abstrakcja cache Springa zapewnia spójną, opartą na adnotacjach warstwę, działającą z różnymi dostawcami. Możesz zacząć od prostej pamięci w developmentcie, przejść do Caffeine na produkcji na pojedynczym węźle, a następnie do Redis przy skalowaniu na wiele instancji — wszystko bez zmian w kodzie serwisów.
Po więcej informacji zajrzyj do oficjalnej dokumentacji Spring Boot Caching i poznaj bardziej zaawansowane wzorce, jak cache-aside z wsparciem reaktywnym w nowszych wydaniach Spring Boot 3.x.
Najpopularniejsze frameworki nie są popularne przypadkiem — cache Springa jest sprawdzony w boju, elastyczny i gotowy na skalę, jakiej potrzebuje Twoja aplikacja. Zacznij od małych kroków, zmierz różnicę i rozwijaj strategię cache wraz ze wzrostem wymagań dotyczących wydajności.
Digital Transformation Strategy for Siemens Finance
Cloud-based platform for Siemens Financial Services in Poland


Gotowy, aby scentralizować swoje know-how z pomocą AI?
Rozpocznij nowy rozdział w zarządzaniu wiedzą — gdzie Asystent AI staje się centralnym filarem Twojego cyfrowego wsparcia.
Umów bezpłatną konsultacjęPracuj z zespołem, któremu ufają firmy z czołówki rynku.





