Spring Data Redis – Caching

Im meinem letzten Beitrag zu Spring Data habe ich eine Serie an Beiträge zu diesem Thema versprochen. Hier nun der zweite Teil. Diesmal verwende ich das Spring Data Modul für den Key-Value-Store Redis.

Seit Version 3.1 beinhaltet das Spring Framework die Cache Abstraction. Damit wird das Zwischenspeichern von Daten deklarativ mittels Annotation (@Cacheable, @CachePut, @CacheEvict, usw.) gesteuert. Spring Data Redis implementiert auf Basis der Cache Abstraction einen Cache Manager der auf Redis zurückgreift. Redis eignet sich dank seiner guten Performance hervorragend als Cache. Der Key-Value Store schafft bis zu 100.000 Schreib- und bis zu 80.000 Lese-Vorgänge in der Sekunde.

Um das Ganze etwas anschaulicher zu machen, habe ich dafür eine Demo-Anwendung erstellt. Diese könnt ihr unter springdata-redis-example anschauen und klonen.

Zunächst etwas zur Konfiguration: Für dieses Beispiel habe ich Spring Boot verwendet. Eine Einleitung zu Spring Boot bekommt ihr im Artikel Microservices und Spring Boot meines Namensvettern Waldemar Schneider.

Spring Boot bietet diverse Starter-Pakete, u.a. auch für Redis. Neben diesem habe ich auch die Pakete für Spring Data JPA und Spring Test eingebunden. Zu sehen in der pom.xml.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.8.RELEASE</version>
  </parent>

  <groupId>de.flaviait.blog.springdata</groupId>
  <artifactId>springdata-redis-example</artifactId>
  <version>0-SNAPSHOT</version>

  <properties>
    <java.version>1.8</java.version>
  </properties>
  
  <dependencies>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <dependency>
      <groupId>org.hsqldb</groupId>
      <artifactId>hsqldb</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
    </dependency>
  </dependencies>
</project>

Damit Redis als Cache verwendet wird, muss ein entsprechender Cache Manager definiert werden. Dazu habe ich in der Klasse Application ein RedisTemplate und den Cache Manager instanziert. Die Besonderheit am RedisTemplate ist, dass die Werte als JSON gespeichert werden. Dies wird über Jackson2JsonRedisSerializer bewerkstelligt. Mit der Annotation @Cacheable wird das Caching aktiviert.

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableCaching
public class Application {

  @Bean
  public RedisTemplate<String, String> template(RedisConnectionFactory factory) {
    final StringRedisTemplate template = new StringRedisTemplate(factory);
    template.setValueSerializer(new Jackson2JsonRedisSerializer<Book>(Book.class));

    return template;
  }

  @Bean
  public CacheManager cacheManager(RedisTemplate<?, ?> template) {
    final RedisCacheManager cacheManager = new RedisCacheManager(template);
    cacheManager.setTransactionAware(true);
    cacheManager.setDefaultExpiration(Duration.ofSeconds(5).getSeconds());
    return cacheManager;
  }

  public static void main(String... args) {
    SpringApplication.run(Application.class, args);
  }
}

Der Serializer arbeitet mit der Klasse Book. Diese Entität besteht aus den Attributen ISBN (Primärschlüssel), Titel und Autoren.
Die Deklaration des Cachings geschieht an der Methode findOne des BookRepository. Das entsprechende Repository wird mittels Spring Data JPA instanziert.

public interface BookRepository extends CrudRepository<Book, String> {

  @Cacheable(key = "#a0", value = "books")
  Book findOne(String isbn);

}

@Cacheable sagt an dieser Stelle aus, dass das Ergebnis der Abfrage mit dem Schlüssel ISBN (#a0) zwischengespeichert werden soll. Das Attribut value gibt den Cache an. Damit ist das Caching bereits komplett konfiguriert. Sollte die Methode findOne weitere Male mit dem gleichen Parameter-Wert aufgerufen werden, so wird keine Abfrage ausgeführt, sondern auf den Wert im Cache zurückgegriffen.

Ein JUnit-Test belegt, dass das Ergebnis im Cache gelandet ist. Die Methode getCache wurde zweimal aufgerufen. zuerst beim Hinzufügen zum Zwischenspeicher und anschließend beim erneuten Aufruf von findOne.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, CachingTest.Config.class })
public class CachingTest {

  private static final String ISBN = "1234567890123";

  @Configuration
  static class Config {

    @Autowired
    private Application application;

    @Bean
    @Primary
    public CacheManager cacheManager(RedisTemplate<?, ?> template) {
      return Mockito.spy(application.cacheManager(template));
    }
  }

  @Autowired
  private CacheManager cacheManager;

  @Autowired
  private BookRepository repository;


  @Test
  public void caching() throws InterruptedException {

    final Book book = new Book(ISBN, "Sample Book", "Waldemar Biller");
    repository.save(book);

    // load book from DB
    final Book loaded = repository.findOne(ISBN);
    assertThat(loaded, not(nullValue()));

    // ensure it gets cached and the cached one
    // is the same as the one from the DB
    final Book cached = repository.findOne(ISBN);

    verify(cacheManager, times(2)).getCache("books");

    assertThat(cached, not(nullValue()));
    assertThat(cached, equalTo(loaded));

    Thread.sleep(5000);

    // ensure the book is evicted after 5 seconds
    final Cache cache = cacheManager.getCache("books");
    assertThat(cache.get(ISBN), nullValue());
  }

}

Ein kurzer Blick auf die Redis-Instanz zeigt, dass das Beispiel-Buch persistiert wurde.

Redis Desktop Manager

Der Cache Manager ist so konfiguriert, dass die Einträge nach 5 Sekunden verworfen werden. Daher sollte der Cache nach einer kurzen Pause zur gegebenen ISBN null zurück liefern.

Achtung: Die hier gewählte Konfiguration dient nur als Beispiel. Durch die Verwendung des Jackson2JsonRedisSerializer ist das Caching auf einen Typen beschränkt. Man sollte über den Einsatz eines allgemeineren Serializers nachdenken (bspw. JdkSerializationRedisSerializer).

Im nächsten Beitrag widme ich mich nochmal dem Modul Spring Data Redis und gehe dabei auf die Messaging Funktionalitäten von Redis ein.

Teilen Sie diesen Beitrag

Das könnte dich auch interessieren …

Schreibe einen Kommentar

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