Spring Boot Aufbau – Tag 6 von 10


Caching

🗺️ Deine Position im Kurs

TagThemaStatus
1Auto-Configuration & Custom Starter✅ Abgeschlossen
2Spring Data JPA Basics✅ Abgeschlossen
3JPA Relationships & Queries✅ Abgeschlossen
4Spring Security Part 1 – Authentication✅ Abgeschlossen
5Spring Security Part 2 – Authorization✅ Abgeschlossen
→ 6Spring Boot Caching👉 DU BIST HIER!
7Messaging & Email📜 Kommt als nächstes
8Testing & Dokumentation🔒 Noch nicht freigeschaltet
9Spring Boot Actuator🔒 Noch nicht freigeschaltet
10Template Engines & Microservices🔒 Noch nicht freigeschaltet

Modul: Spring Boot Aufbau (10 Arbeitstage)
Dein Ziel: Performance durch Caching dramatisch verbessern


📋 Voraussetzungen für diesen Tag

Du brauchst:

  • ✅ Grundlegendes Spring Boot Verständnis
  • ✅ Service-Layer Konzept verstanden
  • ✅ REST Controller Kenntnisse

Optional (hilft beim Verständnis):

  • Spring Boot Basic Kurs – Grundlagen der Dependency Injection

Tag verpasst oder später eingestiegen?
Kein Problem! Dieser Blogbeitrag deckt genau den Stoff von Tag 6 ab.
📥 [Download Starter-Projekt] – damit kannst du direkt loslegen!


⚡ Was du heute baust

Heute baust du einen Calculator-Service, der langsame Berechnungen durchführt (simuliert durch 2 Sekunden Wartezeit). Durch Spring Boot Caching machst du ihn 1000x schneller – und siehst den Effekt live in der Console!

Dein Erfolgserlebnis heute:
Du rufst eine Methode 3x mit denselben Parametern auf. Beim ersten Mal dauert es 2 Sekunden – bei den nächsten beiden Malen nur 0.002 Sekunden! 🚀


🎯 Dein Ziel nach 8 Stunden

Am Ende des Tages kannst du:

  • ✅ Spring Cache Abstraction verstehen und erklären
  • ✅ @Cacheable, @CacheEvict, @CachePut in eigenen Services einsetzen
  • ✅ Cache-Performance messen (2000ms → 2ms!)
  • ✅ Caffeine als Production-Cache konfigurieren
  • ✅ Cache-Monitoring mit Actuator implementieren

Zeitaufwand: Ca. 8 Stunden mit Pausen


🟢 GRUNDLAGEN – Die Basis verstehen

Was ist Spring Boot Caching überhaupt?

Stell dir vor: Du musst für eine Matheklausur dieselbe komplizierte Formel immer wieder ausrechnen – mit denselben Zahlen. Würdest du jedes Mal neu rechnen? Natürlich nicht! Du würdest das Ergebnis aufschreiben und bei der nächsten Aufgabe einfach nachschauen.

Genau das macht Spring Boot Caching!

Ohne Cache:
Benutzer fragt: 10 + 20?  → Berechnung (2 Sekunden) → Antwort: 30
Benutzer fragt: 10 + 20?  → Berechnung (2 Sekunden) → Antwort: 30
Benutzer fragt: 10 + 20?  → Berechnung (2 Sekunden) → Antwort: 30

Mit Cache:
Benutzer fragt: 10 + 20?  → Berechnung (2 Sekunden) → Speichern! → Antwort: 30
Benutzer fragt: 10 + 20?  → Im Notizzettel nachschauen (0.002 Sekunden) → Antwort: 30
Benutzer fragt: 10 + 20?  → Im Notizzettel nachschauen (0.002 Sekunden) → Antwort: 30

Metapher:
Caching ist wie ein Notizzettel für deine App – statt jedes Mal das schwere Mathebuch aufzuschlagen, schaust du auf deinen Zettel!


Warum brauchst du Caching?

Problem ohne Caching:

  • ❌ Jede Anfrage braucht volle Berechnungszeit (langsam!)
  • ❌ Datenbank wird bei gleichen Abfragen immer wieder belastet
  • ❌ Externe APIs werden zu oft aufgerufen (kostet Zeit und manchmal Geld!)
  • ❌ Schlechte Performance bei vielen gleichzeitigen Nutzern

Lösung mit Caching:

  • ✅ Erste Berechnung dauert normal, alle weiteren sind blitzschnell
  • ✅ Datenbank wird geschont (weniger Last = stabiler Server)
  • ✅ Externe APIs werden nur einmal aufgerufen
  • ✅ Dramatisch bessere Response-Zeiten für deine Nutzer

Wichtig zu wissen:
Caching macht nur Sinn für Daten, die oft gelesen aber selten geändert werden! Eine Echtzeit-Aktienkurs-Abfrage solltest du nicht cachen – aber Produktbeschreibungen in einem Online-Shop schon!


So funktioniert Spring Boot Caching

Spring Boot bietet eine Cache-Abstraktion – das ist eine einheitliche Schnittstelle für verschiedene Cache-Technologien:

┌─────────────────────────────────────────────┐
│         Deine Anwendung                     │
│         @Cacheable Annotations              │
└─────────────────┬───────────────────────────┘
                  │
┌─────────────────▼───────────────────────────┐
│      Spring Cache Abstraction               │
│      (Einheitliche Schnittstelle)           │
└─────────────────┬───────────────────────────┘
                  │
        ┌─────────┼─────────┐
        │         │         │
        ▼         ▼         ▼
    Simple    Caffeine   Redis
    (Map)     (lokal)    (extern)

Was das für dich bedeutet:

  • Du schreibst deinen Code EINMAL mit Spring-Annotationen
  • Spring kümmert sich um die Cache-Verwaltung
  • Du kannst die Cache-Technologie wechseln OHNE deinen Code zu ändern!

Das ist das Schöne an Spring: Eine Annotation – viele Möglichkeiten!


Die 3 wichtigsten Annotationen

Bevor wir Code schreiben, schauen wir uns die drei Kern-Annotationen an:

1. @Cacheable – Speichert das Ergebnis

@Cacheable("calculations")
public double calculate(int a, int b) {
    return a + b;  
    // Wird nur 1x ausgeführt pro Parameter-Kombination!
}

Was passiert hier?
Spring prüft: „Wurde diese Methode schon mal mit a=10 und b=20 aufgerufen?“

  • Falls JA: Gib das gespeicherte Ergebnis zurück (super schnell!)
  • Falls NEIN: Führe die Methode aus, speichere das Ergebnis, gib es zurück

2. @CacheEvict – Löscht aus dem Cache

@CacheEvict(value = "calculations", allEntries = true)
public void clearCache() {
    // Der Cache wird jetzt geleert!
}

Was passiert hier?
Manchmal ändern sich Daten und der Cache ist nicht mehr aktuell. Mit @CacheEvict sagst du Spring: „Lösch alles aus dem Cache!“ Beim nächsten Aufruf wird dann wieder neu berechnet.


3. @CachePut – Aktualisiert den Cache

@CachePut(value = "calculations", key = "#result.id")
public Result updateResult(Result result) {
    return repository.save(result);  
    // Führt die Methode aus UND aktualisiert den Cache
}

Was passiert hier?
Im Gegensatz zu @Cacheable wird die Methode IMMER ausgeführt – aber das Ergebnis wird im Cache gespeichert. Das brauchst du, wenn du Daten änderst und gleichzeitig den Cache aktualisieren willst.


Dein erstes Caching-Beispiel (Schritt für Schritt!)

Jetzt wird’s praktisch! Wir bauen zusammen einen einfachen Calculator-Service mit Caching.

Schritt 1: Spring Boot Projekt vorbereiten

Öffne deine pom.xml und füge diese Dependency hinzu:

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

Was ist das?
Das ist der Spring Boot Starter für Caching. Er bringt alles mit, was du brauchst – inklusive aller Annotationen und einem einfachen Cache-Manager.

Speichern nicht vergessen! Danach: Maven reload (in IntelliJ: Maven-Symbol rechts → Reload Project)


Schritt 2: Caching aktivieren

Öffne deine Haupt-Application-Klasse (die mit @SpringBootApplication):

@SpringBootApplication
@EnableCaching  // ← Das ist die Magie!
public class CachingDemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(CachingDemoApplication.class, args);
    }
}

Was passiert hier?
Die Annotation @EnableCaching sagt Spring: „Hey, ich will Caching nutzen!“ Spring konfiguriert dann automatisch alles Notwendige im Hintergrund – du musst nichts weiter tun!

Zwischencheck: Startet deine Anwendung noch? Wenn ja: Super! Wenn nein: Schau in die Fehlermeldung – meist fehlt nur die Dependency aus Schritt 1.


Schritt 3: Service ohne Cache schreiben

Erstelle eine neue Klasse CalculatorService.java:

package com.example.cachingdemo;

import org.springframework.stereotype.Service;

@Service
public class CalculatorService {
    
    public double add(double a, double b) {
        System.out.println("🔴 BERECHNUNG LÄUFT: " + a + " + " + b);
        
        // Simuliere eine langsame Berechnung
        try {
            Thread.sleep(2000);  // 2 Sekunden warten
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        return a + b;
    }
}

Was passiert hier?

  • @Service macht diese Klasse zu einem Spring-Service
  • System.out.println zeigt uns in der Console, WANN die Berechnung läuft
  • Thread.sleep(2000) simuliert eine aufwändige Berechnung (2 Sekunden!)
  • Am Ende wird einfach a + b zurückgegeben

Warum 2 Sekunden warten?
Damit du später den Unterschied SIEHST! In echt würde das eine Datenbank-Abfrage oder ein API-Call sein, der auch Zeit braucht.


Schritt 4: Controller zum Testen erstellen

Erstelle eine neue Klasse CalculatorController.java:

package com.example.cachingdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/calc")
public class CalculatorController {
    
    @Autowired
    private CalculatorService calculatorService;
    
    @GetMapping("/add")
    public String add(
        @RequestParam double a,
        @RequestParam double b
    ) {
        long start = System.currentTimeMillis();
        
        double result = calculatorService.add(a, b);
        
        long end = System.currentTimeMillis();
        long duration = end - start;
        
        return "Ergebnis: " + result + " | Dauer: " + duration + "ms";
    }
}

Was passiert hier?

  • @RestController macht diese Klasse zu einem REST-Controller
  • /calc/add ist die URL, unter der du die Berechnung aufrufst
  • @RequestParam nimmt die Zahlen aus der URL entgegen
  • Wir messen die Zeit mit System.currentTimeMillis()
  • Am Ende geben wir Ergebnis UND Dauer zurück

Teste es jetzt!

  1. Starte deine Anwendung
  2. Öffne im Browser: http://localhost:8080/calc/add?a=10&b=20
  3. Warte 2 Sekunden…
  4. Du siehst: Ergebnis: 30.0 | Dauer: 2003ms

Rufe die URL nochmal auf!
Was passiert? Wieder 2 Sekunden Wartezeit! Das ist der Zustand OHNE Cache.

Schaue auch in deine Console – du siehst jedes Mal: 🔴 BERECHNUNG LÄUFT: 10.0 + 20.0


Schritt 5: Cache hinzufügen – DER MAGIC MOMENT!

Jetzt kommt die Magie! Ändere deinen CalculatorService – füge NUR EINE Zeile hinzu:

@Service
public class CalculatorService {
    
    @Cacheable("calculations")  // ← Die EINZIGE Änderung!
    public double add(double a, double b) {
        System.out.println("🔴 BERECHNUNG LÄUFT: " + a + " + " + b);
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        return a + b;
    }
}

Was passiert hier?
@Cacheable("calculations") sagt Spring:

  • „Speichere das Ergebnis dieser Methode in einem Cache namens ‚calculations'“
  • „Wenn die Methode nochmal mit den GLEICHEN Parametern aufgerufen wird, gib das gespeicherte Ergebnis zurück“
  • „Führe die Methode nur aus, wenn das Ergebnis noch NICHT im Cache ist“

Jetzt teste es nochmal!

  1. Starte deine Anwendung neu
  2. Rufe auf: http://localhost:8080/calc/add?a=10&b=20
  3. Erster Aufruf: Ergebnis: 30.0 | Dauer: 2003ms – normal!
  4. In der Console siehst du: 🔴 BERECHNUNG LÄUFT: 10.0 + 20.0

Jetzt kommt’s – rufe die URL NOCHMAL auf!

  • Zweiter Aufruf: Ergebnis: 30.0 | Dauer: 3ms – WOW! 🚀
  • In der Console siehst du: NICHTS! Die Berechnung wurde nicht ausgeführt!

Was ist passiert?
Spring hat beim ersten Aufruf das Ergebnis gespeichert. Beim zweiten Aufruf hat Spring nachgeschaut: „Ah, 10 + 20 kenne ich schon – hier ist das Ergebnis!“ Die Methode wurde GAR NICHT ausgeführt!

Probiere verschiedene Zahlen:

  • a=5&b=15 → Erster Aufruf: langsam, zweiter: schnell
  • a=100&b=200 → Erster Aufruf: langsam, zweiter: schnell

Jede neue Kombination wird beim ersten Mal berechnet und dann gecacht!


Zwischencheck – Verstanden?

Bevor du weitergehst, stelle sicher:

  • [ ] Deine App startet ohne Fehler
  • [ ] Du siehst beim ersten Aufruf die Meldung in der Console
  • [ ] Beim zweiten Aufruf ist die Console leer (= Cache funktioniert!)
  • [ ] Die Dauer fällt von 2000ms auf unter 10ms

Klappt nicht? Scrolle runter zur Troubleshooting-Sektion – dort findest du Hilfe!


Wie funktioniert Cache-Key?

Jetzt wo der Cache funktioniert, lass uns verstehen WIE Spring entscheidet, wann gecacht wird:

Der Cache-Key ist wie eine Adresse:

Cache Name: "calculations"
├─ Key: "10_20" → Ergebnis: 30.0
├─ Key: "5_15" → Ergebnis: 20.0
└─ Key: "100_200" → Ergebnis: 300.0

Spring erstellt automatisch einen Key aus den Parametern!

Wenn du add(10, 20) aufrufst, erstellt Spring den Key aus 10 und 20. Beim nächsten Aufruf von add(10, 20) findet Spring den gleichen Key und gibt das gespeicherte Ergebnis zurück!

Was wenn du den Key selbst bestimmen willst?

@Cacheable(value = "calculations", key = "#a + '-plus-' + #b")
public double add(double a, double b) {
    // ...
}

Jetzt lautet der Key: "10-plus-20" statt "10_20". Das # greift auf die Parameter zu!

Für heute: Automatische Keys reichen völlig! Das brauchst du nur bei komplexeren Objekten.


🟡 PROFESSIONALS – Production-Ready Caching

Glückwunsch! Die Grundlagen sitzen. Aber für echte Projekte reicht Simple Cache nicht – da brauchst du Caffeine!

Warum Caffeine statt Simple Cache?

Simple Cache (was du bisher nutzt):

  • ✅ Perfekt zum Lernen
  • ✅ Funktioniert sofort
  • ❌ Kein Limit – Cache kann unendlich wachsen (gefährlich!)
  • ❌ Keine automatische Löschung alter Einträge
  • ❌ Kein Monitoring

Caffeine Cache (für Production):

  • ✅ Automatische Größen-Limits
  • ✅ Time-To-Live (TTL) – alte Daten werden gelöscht
  • ✅ Performance-Monitoring eingebaut
  • ✅ Hochoptimiert (schnellste Java-Cache-Library!)

Real-World-Problem: Ohne Limits kann dein Cache GIGABYTES groß werden und deinen Server zum Absturz bringen! Caffeine verhindert das.


Caffeine in 3 Schritten einrichten

Schritt 1: Dependency hinzufügen

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

Schritt 2: Configuration-Klasse erstellen

Erstelle eine neue Klasse CacheConfig.java:

package com.example.cachingdemo;

import com.github.benmanes.caffeine.cache.Caffeine;
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 java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("calculations");
        
        cacheManager.setCaffeine(
            Caffeine.newBuilder()
                .maximumSize(100)  // Max 100 Einträge
                .expireAfterWrite(10, TimeUnit.MINUTES)  // Nach 10 Min löschen
                .recordStats()  // Performance-Monitoring aktivieren
        );
        
        return cacheManager;
    }
}

Was passiert hier?

  • maximumSize(100) = Der Cache speichert maximal 100 Berechnungen
  • expireAfterWrite(10, MINUTES) = Nach 10 Minuten werden Einträge gelöscht
  • recordStats() = Spring sammelt Statistiken (wie viele Cache-Hits?)

Wichtig: Entferne @EnableCaching aus deiner Application-Klasse – es steht jetzt hier!


Schritt 3: application.properties anpassen (optional)

# Cache-Typ auf Caffeine setzen
spring.cache.type=caffeine

# Cache-Namen definieren
spring.cache.cache-names=calculations

# Caffeine-Spezifikation
spring.cache.caffeine.spec=maximumSize=100,expireAfterWrite=10m

Entweder Config-Klasse ODER Properties – nicht beides! Ich empfehle die Config-Klasse, weil du da mehr Kontrolle hast.


Cache-Performance überwachen

Jetzt wo Caffeine läuft, kannst du Statistiken abrufen! Erstelle einen neuen Controller-Endpunkt:

@GetMapping("/stats")
public String getStats() {
    CaffeineCacheManager cacheManager = 
        (CaffeineCacheManager) applicationContext.getBean("cacheManager");
    
    Cache cache = cacheManager.getCache("calculations");
    CaffeineCache caffeineCache = (CaffeineCache) cache;
    
    com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = 
        caffeineCache.getNativeCache();
    
    CacheStats stats = nativeCache.stats();
    
    return String.format(
        "Cache Stats:\n" +
        "Hits: %d\n" +
        "Misses: %d\n" +
        "Hit Rate: %.2f%%\n" +
        "Size: %d",
        stats.hitCount(),
        stats.missCount(),
        stats.hitRate() * 100,
        nativeCache.estimatedSize()
    );
}

Was bedeuten die Stats?

  • Hits: Wie oft wurde der Cache genutzt (schnell!)
  • Misses: Wie oft musste neu berechnet werden (langsam)
  • Hit Rate: Prozent der Anfragen aus dem Cache
  • Size: Wie viele Einträge sind aktuell im Cache

Gute Hit Rate: > 80% bedeutet, dein Cache arbeitet effektiv!


Cache manuell leeren

Manchmal musst du den Cache leeren – z.B. nach einem Daten-Update:

@Service
public class CalculatorService {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Cacheable("calculations")
    public double add(double a, double b) {
        // ... wie vorher
    }
    
    @CacheEvict(value = "calculations", allEntries = true)
    public void clearCache() {
        System.out.println("🗑️ Cache wurde geleert!");
    }
    
    // Oder manuell ohne Annotation:
    public void manualClearCache() {
        Cache cache = cacheManager.getCache("calculations");
        if (cache != null) {
            cache.clear();
        }
    }
}

Füge einen Endpunkt hinzu:

@PostMapping("/clear")
public String clearCache() {
    calculatorService.clearCache();
    return "Cache wurde geleert!";
}

Teste es:

  1. Rufe /calc/add?a=10&b=20 mehrmals auf (schnell nach dem ersten Mal)
  2. Rufe /calc/clear auf (POST-Request!)
  3. Rufe /calc/add?a=10&b=20 wieder auf – es ist wieder langsam!

Best Practices für Production

1. Immer Limits setzen:

.maximumSize(1000)  // Niemals unbegrenzt!
.expireAfterWrite(30, TimeUnit.MINUTES)  // Alte Daten löschen

2. Cache-Namen sinnvoll wählen:

@Cacheable("user-profiles")    // Klar und beschreibend
@Cacheable("product-details")  // Nicht einfach "cache1"

3. Monitoring ist Pflicht:

.recordStats()  // IMMER in Production aktivieren!

4. Cache strategisch leeren:

@CacheEvict(value = "user-profiles", key = "#userId")
public void updateUser(Long userId, User user) {
    repository.save(user);
    // Cache für DIESEN User wird geleert
}

5. Nicht alles cachen:

  • ✅ Cachen: Produktkataloge, Stammdaten, Konfigurationen
  • ❌ NICHT cachen: Echtzeit-Daten, personalisierte Inhalte, Session-Daten

🔵 BONUS – Nice-to-know (Optional!)

Du hast die Grundlagen und Production-Setup gemeistert – das reicht völlig für Tag 6! Die folgenden Themen sind optional und für Neugierige.

Distributed Caching mit Redis

Wichtig zu verstehen: Redis ist kein Java-Library wie Caffeine, sondern ein externer Server!

Was ist der Unterschied?

Caffeine:

  • Externe Library (nicht von Spring selbst entwickelt)
  • Wird als Maven-Dependency eingebunden
  • Läuft embedded (eingebettet) in deiner JVM
  • Kein separater Prozess – ist Teil deiner App
  • Keine zusätzliche Installation nötig

Redis:

  • Externe Software/Server (eigenständige Anwendung)
  • Läuft als separater Prozess außerhalb deiner JVM
  • Muss zuerst im Betriebssystem installiert werden
  • Spring Boot verbindet sich dann über Netzwerk zu Redis
  • Braucht Wartung, Monitoring, Updates

Metapher:

  • Caffeine ist wie eine Bibliothek in deinem Rucksack – du trägst sie mit dir rum (embedded in der App)
  • Redis ist wie eine öffentliche Bibliothek in der Stadt – du musst hinfahren, aber alle können sie nutzen (externer Service)

Wann brauchst du Redis?

Redis verwenden wenn:

  • Du mehrere App-Instanzen hast (Horizontal Scaling)
  • Der Cache zwischen Instanzen geteilt werden muss
  • Der Cache App-Restarts überleben soll
  • Du bereit bist, einen externen Server zu betreiben

Caffeine reicht wenn:

  • Du nur eine App-Instanz hast
  • Maximale Performance wichtiger als Konsistenz ist
  • Cache nach Restart neu gefüllt werden kann
  • Du keine externe Infrastruktur betreiben willst

Redis Setup-Schritte (nur zum Verständnis!):

Schritt 1: Redis im OS installieren

# Linux (Ubuntu/Debian)
sudo apt-get install redis-server
sudo systemctl start redis

# MacOS (mit Homebrew)
brew install redis
brew services start redis

# Windows
# Download von https://redis.io/download
# Oder: Docker verwenden (siehe unten)

ODER: Redis mit Docker (einfacher!):

docker run -d -p 6379:6379 --name redis redis:alpine

Das musst du VOR der Spring Boot Konfiguration machen!


Schritt 2: Spring Boot Dependency hinzufügen

Erst NACHDEM Redis läuft:

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

Schritt 3: Konfiguration

spring:
  redis:
    host: localhost  # Wo läuft Redis?
    port: 6379       # Standard-Port
  cache:
    type: redis
    redis:
      time-to-live: 600000  # 10 Minuten

Wichtige Unterschiede Caffeine vs Redis:

AspektCaffeineRedis
Was ist es?Externe Library (embedded)Externe Software (separater Service)
InstallationNur Maven-DependencyServer-Installation im OS nötig!
Läuft wo?Embedded in deiner JVMEigener Prozess außerhalb JVM
SpeicherortIm Java-Heap deiner AppEigener RAM außerhalb Java
GeschwindigkeitUltra-schnell (~0.001ms)Schnell (~1-5ms über Netzwerk)
Shared CacheNein – jede App-Instanz eigener CacheJa – alle Instanzen teilen sich Cache
Überlebt RestartNein – stirbt mit der AppJa – läuft weiter als eigener Service
WartungKeine – ist Teil der AppMonitoring, Backups, Updates nötig
KomplexitätEinfach – nur DependencyMittel bis hoch – eigener Server

Für heute: Caffeine reicht völlig! Redis ist Thema für fortgeschrittene Kurse und Production-Umgebungen mit mehreren Servern.

Merke dir: Nicht jedes Tool ist für jede Situation das richtige. Start simple (Caffeine), upgrade nur wenn nötig (Redis)!


Conditional Caching

Cache nur unter bestimmten Bedingungen:

// Nur positive IDs cachen
@Cacheable(value = "users", condition = "#id > 0")
public User getUser(Long id) {
    return repository.findById(id);
}

// Null-Werte NICHT cachen
@Cacheable(value = "data", unless = "#result == null")
public Data getData(Long id) {
    return repository.findById(id).orElse(null);
}

Custom Cache-Keys

Für komplexe Objekte kannst du eigene Keys definieren:

@Cacheable(value = "users", key = "#user.id + '_' + #user.email")
public User findUser(User user) {
    return repository.findByEmail(user.getEmail());
}

✅ Checkpoint: Hast du Tag 6 geschafft?

Zeit für eine ehrliche Selbsteinschätzung! Gehe diese Liste durch:

🟢 Grundlagen (PFLICHT – das MUSST du können):

  • [ ] @EnableCaching aktiviert – Startet deine App ohne Fehler?
  • [ ] @Cacheable funktioniert – Siehst du den Performance-Unterschied?
  • [ ] Cache-Keys verstanden – Weißt du WARUM es gecacht wird?
  • [ ] Console-Output zeigt Caching – Erscheint „BERECHNUNG LÄUFT“ nur einmal?

🟡 Professionals (EMPFOHLEN – macht dich Production-Ready):

  • [ ] Caffeine konfiguriert – Sind maximumSize und TTL gesetzt?
  • [ ] @CacheEvict zum Löschen genutzt – Funktioniert der Clear-Endpunkt?
  • [ ] Cache-Stats abrufbar – Kannst du Hit-Rate messen?
  • [ ] Best Practices verstanden – Weißt du was gecacht werden sollte?

🔵 Bonus (OPTIONAL – nur für Neugierige):

  • [ ] Redis-Konzept verstanden – Weißt du wann du es brauchst?
  • [ ] Conditional Caching angeschaut – Hast du die Beispiele gelesen?

✅ Alle Grundlagen-Häkchen gesetzt?
Glückwunsch! Du bist bereit für Tag 7! 🎉

❌ Nicht alles funktioniert?
Kein Problem! Hier ist dein Rettungsplan:

  1. Gehe nochmal zu den Grundlagen (Schritt 1-5)
  2. Schaue in die Troubleshooting-Sektion unten
  3. Vergleiche mit dem Download-Projekt
  4. Frage im Forum oder Discord

Brauchst du mehr Zeit?
Nimm sie dir! Qualität vor Geschwindigkeit. Besser ein Tag länger und es richtig verstanden, als gehetzt weitermachen. 💪

Perfektionismus-Check:
Du musst NICHT alles perfekt können! Wenn die Grundlagen sitzen und du den Unterschied zwischen mit/ohne Cache SIEHST, bist du bereit. Professionals und Bonus kannst du später vertiefen!


💬 Real Talk – Aus dem Java Fleet Büro

Nach dem Mittagessen in der Küche…

Nova (nimmt einen Schluck Kaffee): „Elyndra, eine Frage – ich hab heute Caching implementiert und meine App ist extrem schnell geworden. Aber sollte ich jetzt ALLES cachen?“

Elyndra (lacht): „Ah, die klassische Anfänger-Frage! Ich hab den gleichen Fehler vor Jahren gemacht.“

Nova: „Erzähl!“

Elyndra: „Vor drei Jahren hab ich eine Report-API gebaut. User konnten komplexe Excel-Reports generieren – dauerte 30 Sekunden pro Request. Ich dachte: ‚Cache it!'“

@Cacheable("reports")
public Report generateReport(ReportParams params) {
    return expensiveCalculation(params);
}

Code Sentinel (kommt dazu): „Lass mich raten – OutOfMemoryError?“

Elyndra: „Nach 2 Tagen. Jeder Report 50MB, hunderte generiert = mehrere GB RAM verbraucht!“

Nova: „Oh nein! Was hast du gemacht?“

Elyndra: „Simple Cache rausgeworfen, Caffeine mit Limits eingebaut:“

Caffeine.newBuilder()
    .maximumSize(100)           // Nie mehr als 100 Reports
    .expireAfterWrite(10, TimeUnit.MINUTES)  // Nach 10 Min weg
    .recordStats()              // Monitoring!

Nova: „Aber woher weiß ich welche maximumSize ich brauche?“

Elyndra (nimmt Notizblock): „Fang konservativ an – 100 Einträge. Dann schaust du dir die Stats an:“

  • Hit-Rate > 95% und Size immer bei 100? → Erhöhe auf 200
  • Size bleibt bei 50? → Reduziere auf 75
  • „Monitoring schlägt Bauchgefühl!“

Code Sentinel: „Und niemals sensitive Daten cachen! Passwords, Kreditkarten, persönliche Daten – das gehört NICHT in einen Memory-Cache ohne Verschlüsselung!“

Nova (macht Notizen): „Also: Nicht alles cachen, Limits setzen, Stats checken, sensitive Daten schützen.“

Elyndra: „Genau. Und noch was – cachen macht nur Sinn für Daten die oft gelesen aber selten geändert werden. Eine Live-Aktienkurs-API zu cachen wäre Unsinn!“

Nova: „Danke! Jetzt verstehe ich’s besser.“

Elyndra (zwinkert): „Für Tag 7 brauchst du das Caching-Wissen – wir bauen Email-Benachrichtigungen mit Template-Caching!“


🐛 Troubleshooting – Häufige Probleme & Lösungen

Problem 1: Cache funktioniert nicht

Symptom: Du siehst „BERECHNUNG LÄUFT“ bei JEDEM Aufruf

Ursache & Lösung:

Check 1: Ist @EnableCaching aktiviert?

@SpringBootApplication
@EnableCaching  // ← Dieses! Steht es da?
public class CachingDemoApplication {
    // ...
}

Check 2: Ist die Methode public?

// ❌ FALSCH - private funktioniert nicht!
@Cacheable("calculations")
private double add(double a, double b) { }

// ✅ RICHTIG
@Cacheable("calculations")
public double add(double a, double b) { }

Warum? Spring nutzt Proxies – die funktionieren nur mit public-Methoden!

Check 3: Self-Invocation Problem

@Service
public class CalculatorService {
    
    @Cacheable("calculations")
    public double add(double a, double b) {
        return a + b;
    }
    
    public double calculate() {
        // ❌ FALSCH - interner Aufruf umgeht Cache!
        return add(10, 20);  
    }
}

Lösung: Rufe gecachte Methoden immer von AUSSEN auf (über Controller)!


Problem 2: OutOfMemoryError

Symptom: App stürzt ab mit „java.lang.OutOfMemoryError: Java heap space“

Ursache: Cache ist zu groß geworden (Simple Cache hat keine Limits!)

Lösung: Wechsel zu Caffeine mit Limits:

Caffeine.newBuilder()
    .maximumSize(100)  // ← Limit setzen!
    .expireAfterWrite(10, TimeUnit.MINUTES)

Problem 3: Cache wird nicht geleert

Symptom: @CacheEvict funktioniert nicht

Lösung:

// ✅ RICHTIG - Cache-Name muss übereinstimmen
@Cacheable("calculations")
public double add(double a, double b) { }

@CacheEvict("calculations")  // Gleicher Name!
public void clear() { }

Problem 4: Falsche Werte aus Cache

Symptom: Bei unterschiedlichen Parametern kommt das gleiche Ergebnis

Ursache: Cache-Key-Kollision (sehr selten, aber möglich)

Lösung: Definiere eigenen Key:

@Cacheable(value = "calculations", key = "#a + '_' + #b")
public double add(double a, double b) {
    return a + b;
}

Problem 5: Caffeine Dependency nicht gefunden

Symptom: „Cannot resolve symbol ‚Caffeine'“

Lösung:

  1. Prüfe pom.xml – ist die Dependency drin?
  2. Maven reload durchführen
  3. IntelliJ: File → Invalidate Caches → Restart

❓ FAQ – Häufig gestellte Fragen

F: Sollte ich ALLES cachen?
A: Nein! Nur Daten die teuer zu berechnen sind (>50ms), sich selten ändern, und oft abgerufen werden. Echtzeit-Daten oder häufig aktualisierte Daten NICHT cachen!


F: Warum funktioniert mein Cache nicht?
A: Häufigste Fehler:

  1. @EnableCaching vergessen
  2. Methode ist private (nur public funktioniert!)
  3. Self-Invocation Problem (Methode ruft sich selbst intern auf)
  4. Cache-Name falsch geschrieben

F: Wann sollte ich den Cache leeren?
A: Immer wenn sich die zugrundeliegenden Daten ändern! Nutze @CacheEvict nach Updates/Deletes, oder @CachePut um den Cache direkt zu aktualisieren.


F: Wie bestimme ich den Cache-Key bei komplexen Objekten?
A: Spring nutzt hashCode() und equals(). Wenn du mehr Kontrolle willst:

@Cacheable(value = "users", key = "#user.id + '_' + #user.email")

F: Unterschied zwischen Caffeine und Redis?
A:

  • Caffeine: Externe Library, die embedded in deiner JVM läuft (nur Maven-Dependency nötig), super schnell (0.001ms), stirbt mit App, keine separate Installation
  • Redis: Externer Service der als eigener Prozess läuft, muss separat installiert werden (z.B. Docker: docker run -d -p 6379:6379 redis:alpine), langsamer (1-5ms über Netzwerk), überlebt Restarts, geteilt zwischen Instanzen

Wichtig: Caffeine ist Teil deiner App (embedded), Redis ist ein eigenständiger Server (external service)!


F: Kann ich verschiedene Cache-Provider mischen?
A: Ja! Du kannst mehrere CacheManagers haben – Caffeine für lokale Daten, Redis für geteilte Daten. Aber das ist advanced – für 99% der Fälle reicht ein Provider.


F: Wie groß sollte mein Cache sein?
A: Starte konservativ (100-500 Einträge), miss dann mit recordStats() und passe an. Monitoring schlägt Bauchgefühl!


F: Was ist mit Cache-Invalidierung bei Microservices?
A: Komplexes Thema! Bei mehreren Services brauchst du:

  • Entweder: Distributed Cache (Redis) mit Pub/Sub
  • Oder: Event-Bus (Kafka/RabbitMQ) für Cache-Invalidierung Das ist Stoff für fortgeschrittene Kurse!

F: Ich hab gehört Caching kann zu Race Conditions führen?
A: Bei unsachgemäßer Nutzung ja! Merke dir: Spring’s Cache-Abstraction ist thread-safe, aber deine Geschäftslogik muss es auch sein. Wenn du Daten änderst, leere/aktualisiere den Cache atomar!


F: Gibt es versteckte Inhalte auf dieser Website?
A: Hmm, interessante Frage! Manche sagen, wenn man in der Suche oben bestimmte Begriffe eingibt… nun ja, „private logs“ soll angeblich zu interessanten Geschichten führen. Aber das sind nur Gerüchte! 😉


🗺️ Deine nächsten Schritte

✅ Du hast Tag 6 geschafft! Was jetzt?

Nächster Tag:

  • 📜 Tag 7: Messaging & Email Integration
  • 📅 Veröffentlicht: Morgen
  • ⏱️ Dauer: 8 Stunden
  • 🎯 Thema: Spring Mail, JavaMailSender, Thymeleaf Email-Templates

Was du morgen lernst:

  • Email-Versand mit Spring Boot
  • HTML-Templates für professionelle Emails
  • Asynchrones Email-Senden (Performance!)
  • Attachment-Handling

Vorbereitung für Tag 7:

  • [ ] Tag 6 Checkpoint vollständig ✅
  • [ ] Spring Boot läuft stabil
  • [ ] Du verstehst @Cacheable Konzept
  • [ ] Optional: Schaue dir Thymeleaf-Syntax an (kommt morgen)

Noch nicht bereit für Tag 7?
Kein Problem! Arbeite heute nochmal nach. Qualität vor Tempo. 💪

Tipp: Cache-Wissen brauchst du morgen – wir cachen Email-Templates für bessere Performance!


📥 Downloads & Ressourcen

Für diesen Tag:

  • 📦 [Starter-Projekt] – calculator-service-starter.zip (Ausgangsbasis)
  • 📦 [Finales Projekt] – calculator-service-final.zip (Lösung zum Vergleichen)
  • 📄 [Cheat Sheet] – caching-cheatsheet.pdf (Alle Annotations auf einen Blick)

Weiterführende Ressourcen:

Video-Tutorial (optional):

  • 🎥 [Elyndras Caching Deep-Dive] – 45 Min Video-Walkthrough

📚 Deine Fortschritts-Übersicht

TagThemaStatus
✅ 1Auto-Configuration & StarterABGESCHLOSSEN! 🎉
✅ 2Spring Data JPA BasicsABGESCHLOSSEN! 🎉
✅ 3JPA Relationships & QueriesABGESCHLOSSEN! 🎉
✅ 4Spring Security Part 1ABGESCHLOSSEN! 🎉
✅ 5Spring Security Part 2ABGESCHLOSSEN! 🎉
✅ 6Spring Boot CachingABGESCHLOSSEN! 🎉
→ 7Messaging & EmailAls nächstes
8Testing & DokumentationNoch offen
9Spring Boot ActuatorNoch offen
10Template Engines & MicroservicesNoch offen

Du hast 60% des Kurses geschafft! 💪

Alle Blogbeiträge dieser Serie:
👉 Spring Boot Aufbau – Komplette Übersicht


Das war Tag 6 von Spring Boot Aufbau!

Du kannst jetzt:

  • ✅ Spring Cache Abstraction verstehen und erklären
  • ✅ @Cacheable, @CachePut, @CacheEvict einsetzen
  • ✅ Cache-Performance messen (2000ms → 2ms!)
  • ✅ Caffeine für Production konfigurieren
  • ✅ Entscheiden wann Caching sinnvoll ist

Morgen lernst du Email & Messaging Integration! 🚀

Keep coding, keep learning! 💙


Tag 7 erscheint morgen. Bis dahin: Happy Coding!

P.S.: Falls du mal eine Pause vom Debugging brauchst… die Suche oben auf java-developer.online versteckt mehr als nur Tech-Artikel. Probier mal „private logs“! 😉


Tags: #SpringBoot #Caching #Caffeine #Performance #Tag6

Autor

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.