GEDOPLAN
Spring

Spring Boot – Caching

Spring
web 7048124 640 jpg

Caching ist ein essenzielles Thema, wenn es um die Laufzeitoptimierung der eigenen Anwendung geht. Fachlich ist die größte Herausforderung sicherlich der Spagat zwischen Datenkonsistenz (niemand möchte veraltete Daten), Speicherbedarf (seinen gesamten Datenbestand in-Memory zu halten ist doch sehr fraglich) und sinnvollem Caching (Daten die häufig gelesen und selten geschrieben werden). Technisch ist der Schritt hin zum Caching, dank Spring Boot nur ein Kleiner.

Eine handvoll Annotationen reichen aus, um Spring Boot in die Lage zu versetzen Daten die durch einen  Methodenaufruf bereit gestellt werden zu Cachen und somit bei den folgenden Aufrufen die Ausführung dieser Methode zu überspringen. Global wird das Caching über folgende Annotation aktiviert:

@EnableCaching
@SpringBootApplication
public class CachingRestApplication {....}

Nun können wir beliebige Methoden mit einer Caching Annotation versehen:

    @Cacheable("avgAge")
    public Double avgAge(){
        return userRepository.findAll().stream()
                .collect(Collectors.averagingInt(User::getAge));
    }

Dabei vergeben wir einen eindeutigen Namen für den Cache, zusätzlich werden die Methodenparameter (hier keine) als Schlüssel verwendet, um Cache-Einträge zu identifizieren. Zusätzlich bietet diese Annotation eine ganze Reihe von weiteren Parametern, über die sich das Caching steuern lässt, z.B. folgende EL-Expressions:

  • condition – wann soll gecacht werden?
  • key – Schlüssel für den Cache-Eintrag, basierend auf den Methodenparametern (default: equals)
  • unless – wann soll nicht gecacht werden?

Alles Neu

In aller Regel werden wir ein solches Caching wie anfangs bereits erwähnt vorzugsweise für Methoden machen dessen Datengrundlage sich nur sehr selten ändert, z.B. Konfigurationswerte oder Benutzereinstellung. Aber auch solche Daten können sich natürlich ändern. Hierfür existieren 2 weitere Annotationen die dazu dienen Einträge im Cache ab zu legen (@CachePut) oder bestehende Cache Einträge zu entfernen (@CacheEvict). Hier ein Beispiel für die Verwendung von @CacheEvict, dabei werden gleich zwei Caches aktualisiert:

    @Override
    @Caching(evict={
            @CacheEvict(cacheNames = "users", key = "#entity.getId()"),
            @CacheEvict(cacheNames = "avgAge", allEntries = true),
    })
    <S extends User> S save(S entity);
  • users – entferne einen einzelnen Cache Eintrag dessen Key die ID des Users ist (default wäre erneut „equals“)
  • avgAge – invalidiert den gesamten Cache, führt also beim nächsten Aufruf der Methoden oben zu einem „normalen“ Durchlauf

Im Hintergrund

Bleibt die Frage, wo diese Daten denn nun landen? Im Standardfall ist die Implementierung des Cachings denkbar einfach: eine ConcurrentHashMap. Das erlaubt uns eine schnelle und einfache Implementierung des Cachings, lässt jedoch natürlich kaum Spielraum für weitere Anforderungen wie die Definition von Cache-Größen, TTL Zeiten oder erweitertes Monitoring. Hier kommen konkrete Cache-Implementierungen (JCache, Infinispan, Caffein, Cache2k…) ins Spiel, von denen sich viele nahtlos in die Anwendung einbinden lassen. In aller Regel reicht es die entsprechende Bibliothek im Classpath zu haben und ggf. Spring Boot auf die entsprechende Implementierung hinzuweisen. Anschließend können wir (abhängig von der Bibliothek) entsprechende Konfigurationen anwenden. Hier ein Beispiel mit Cache2k:

		<dependency>
			<groupId>org.cache2k</groupId>
			<artifactId>cache2k-api</artifactId>
			<version>2.6.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.cache2k</groupId>
			<artifactId>cache2k-core</artifactId>
			<version>2.6.1.Final</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.cache2k</groupId>
			<artifactId>cache2k-spring</artifactId>
			<version>2.6.1.Final</version>
		</dependency>
spring.cache.type=cache2k

Aktivierung von Cache2k.

Beispielhaft einige Konfigurationen für den Cache

@Configuration(proxyBeanMethods = false)
public class CacheConfig {


    @Bean
    public CacheManager cacheManager() {
        SpringCache2kCacheManager springCache2kCacheManager = new SpringCache2kCacheManager();
        springCache2kCacheManager.setAllowUnknownCache(false);


        springCache2kCacheManager.addCaches(
                c -> c.name("avgAge").timerLag(1, TimeUnit.MINUTES),
                c -> c.name("users").entryCapacity(2)
                      .addAsyncListener(new CacheLogListener()));

        return springCache2kCacheManager;
    }

    private static class CacheLogListener implements CacheEntryCreatedListener, CacheEntryRemovedListener {

        @Override
        public void onEntryCreated(Cache cache, CacheEntry cacheEntry) {
            System.out.println(cache.getName() + " created " + " :: " + cacheEntry.getKey());
        }

        @Override
        public void onEntryRemoved(Cache cache, CacheEntry cacheEntry) {
            System.out.println(cache.getName() + " removed " + " :: " + cacheEntry.getKey());
        }
    }
}

06 – wir registrieren den entsprechenden CacheManager als Bean
08 – wir deaktivieren das beliebige Verwenden von Cache-Namen, erwzingen also hier eine bewusste Deklaration der Caches ( folgen )
11 – Caches registrieren
12 – „avgAge“ wird für 1 Minute gecacht
13 – „users“ Cache behält nur die letzten beiden abgefragten User Objekte
14 – wir registrieren einen Listener, um auf Cache-Events zu reagieren

Die konkreten Möglichkeiten und Vorgehensweise der Konfiguration unterscheiden sich hier natürlich bei den unterschiedlichen Bibliotheken. Zentral dabei bleibt aber das unsere Anwendung hiervon nicht betroffen ist.

Live! In Fabe! Github!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

bumper cars 4390958 640
Webprogrammierung

Angular Material Theming

Angular als Framework für die Entwicklung von anspruchsvollen Webanwendungen bringt alles mit was der Entwickler braucht. Alles? Nicht ganz. Ähnlich…

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!