Spring Boot 3 with Redis Cache

This example explained about spring boot3 with cache redis server. Caching is reduce the number of calls made to your endpoint and also improve the latency of requests to your API.

Sopheaktra

Eang Sopheaktra

March 05 2024 10:57 am

0 392

Why HTTP Caching Matters for APIs?

Caching is reduce the number of calls made to your endpoint and also improve the latency of requests to your API.

  • Performance improvement
  • Reduced server load
  • Bandwidth optimization
  • Scalability
  • Handling traffic spikes

When should using Cache for APIs?

Using cache when your data is not volatile real-time data.

Requirements

The fully fledged server uses the following:

  • Spring Framework
  • SpringBoot
  • Log4j2
  • Spring Data JPA
  • h2 Database
  • Spring Data Redis
  • Mapstruct
  • Lombok

Add Dependencies to pom.xml

There are a number of third-party dependencies used in the project. Browse the Maven pom.xml file for details of libraries and versions used.

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
		<exclusions>
			<exclusion>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-logging</artifactId>
			</exclusion>
		</exclusions>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-log4j2</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-data-redis</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springdoc</groupId>
		<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
		<version>2.0.2</version>
	</dependency>

	<dependency>
		<groupId>com.h2database</groupId>
		<artifactId>h2</artifactId>
		<scope>runtime</scope>
		<version>2.2.224</version>
	</dependency>

	<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct</artifactId>
		<version>${mapstruct.version}</version>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>${lombok.version}</version>
		<optional>true</optional>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

Create RedisConfig class

package com.tra22.spring.redis.config;

import java.time.Duration;

import lombok.AccessLevel;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
//import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
import org.springframework.util.StringUtils;

@Configuration
@ConfigurationProperties(prefix  = "redis")
public class RedisConfig {
  @Setter
  private String host;
  @Setter
  private int port;
  @Setter
  private String username;
  @Setter
  private String password;
  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {
//    RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port);
    RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
    configuration.setHostName(host);
    configuration.setPort(port);
    if(StringUtils.hasText(username) && StringUtils.hasText(password)) {
      configuration.setUsername(username);
      configuration.setPassword(password);
    }
    return new LettuceConnectionFactory(configuration);
  }
  @Bean
  public RedisCacheManager cacheManager() {
    RedisCacheConfiguration cacheConfig = myDefaultCacheConfig(Duration.ofMinutes(10)).disableCachingNullValues();

    return RedisCacheManager.builder(redisConnectionFactory())
        .cacheDefaults(cacheConfig)
        .withCacheConfiguration("books", myDefaultCacheConfig(Duration.ofMinutes(5)))
        .withCacheConfiguration("book", myDefaultCacheConfig(Duration.ofMinutes(1)))
        .build();
  }

  private RedisCacheConfiguration myDefaultCacheConfig(Duration duration) {
    return RedisCacheConfiguration
        .defaultCacheConfig()
        .entryTtl(duration)
        .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
  }
}

Create BookService interface

package com.tra22.spring.redis.service.interf;

import com.tra22.spring.redis.dto.book.BookDto;
import com.tra22.spring.redis.dto.book.CreateBookDto;
import com.tra22.spring.redis.dto.book.UpdateBookDto;

import java.util.List;

public interface IBookService {
    List<BookDto> findAll();
    List<BookDto> findByTitleContaining(String title);
    BookDto findById(long id);
    BookDto save(CreateBookDto book);
    BookDto update(UpdateBookDto book);
    void deleteById(long id);
    void deleteAll();
    List<BookDto> findByPublished(boolean isPublished);
}

Create BookService to implement BookService interface

We can see implement some anotation of cache on below code:

package com.tra22.spring.redis.service;

import com.tra22.spring.redis.dto.book.BookDto;
import com.tra22.spring.redis.dto.book.CreateBookDto;
import com.tra22.spring.redis.dto.book.UpdateBookDto;
import com.tra22.spring.redis.exception.NotFoundEntityException;
import com.tra22.spring.redis.mapper.book.IBookMapper;
import com.tra22.spring.redis.model.Book;
import com.tra22.spring.redis.repository.BookRepository;
import com.tra22.spring.redis.service.interf.IBookService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@EnableCaching
@RequiredArgsConstructor
public class BookService implements IBookService {
  private final BookRepository bookRepository;
  private final IBookMapper bookMapper;

  @Cacheable("books")
  public List<BookDto> findAll() {
    doLongRunningTask();
    return bookMapper.mapReverseList(bookRepository.findAll());
  }

  @Cacheable("books")
  public List<BookDto> findByTitleContaining(String title) {
    doLongRunningTask();
    return bookMapper.mapReverseList(bookRepository.findByTitleContaining(title));
  }

  @Cacheable("book")
  public BookDto findById(long id) {
    doLongRunningTask();
    return bookMapper.mapReverse(bookRepository.findById(id).orElseThrow(() -> new NotFoundEntityException("Not Found Book!")));
  }
  public BookDto save(CreateBookDto book) {
    Book _book = bookMapper.mapCreateBook(book);
    bookRepository.save(_book);
    return bookMapper.mapReverse(_book);
  }

  @CacheEvict(value = "book", key = "#book.id")
  public BookDto update(UpdateBookDto book) {
    bookRepository.findById(book.getId()).orElseThrow(() -> new NotFoundEntityException("Not Found Book!"));
    return bookMapper.mapReverse(bookRepository.save(bookMapper.mapUpdateBook(book)));
  }

  @CacheEvict(value = "book", key = "#id")
  public void deleteById(long id) {
    bookRepository.deleteById(id);
  }

  @CacheEvict(value = { "book", "books", "published_books" }, allEntries = true)
  public void deleteAll() {
    bookRepository.deleteAll();
  }

  @Cacheable("published_books")
  public List<BookDto> findByPublished(boolean isPublished) {
    doLongRunningTask();
    return bookMapper.mapReverseList(bookRepository.findByPublished(isPublished));
  }
  private void doLongRunningTask() {
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Create CacheService (interface and class)

Interface: 

package com.tra22.spring.redis.service.interf;

public interface ICacheService {
    void evictCacheValueByKey(String cacheName, String key);
    void evictCacheValueByName(String cacheName);
    void evictCacheValues();
    Object getCacheValueByKey(String cacheName, String key);
}

Class to implement cachservice interface:

package com.tra22.spring.redis.service;

import com.tra22.spring.redis.exception.NotFoundEntityException;
import com.tra22.spring.redis.service.interf.ICacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Service
@RequiredArgsConstructor
@Log4j2
public class CacheService implements ICacheService {
    private final CacheManager cacheManager;

    @Override
    public void evictCacheValueByKey(String cacheName, String key) {
        Cache cache = cacheManager.getCache(cacheName);
        if(cache == null) throw new NotFoundEntityException("Cache not found.");
        cache.evictIfPresent(key);
        log.info("value is evicted from cache {} and key {}", cacheName, key);
    }

    @Override
    public void evictCacheValueByName(String cacheName) {
        Objects.requireNonNull(cacheManager.getCache(cacheName)).clear();
        log.info("value is evicted from cache {}", cacheName);
    }

    @Override
    public void evictCacheValues() {
        cacheManager.getCacheNames().clear();
        log.info("All cache values is evicted from cache");
    }

    @Override
    public Object getCacheValueByKey(String cacheName, String key) {
        Cache cache = cacheManager.getCache(cacheName);
        if(cache == null) throw new NotFoundEntityException("Cache not found.");
        return cache.get(key, Object.class);
    }
}

Create controller for cach management 

package com.tra22.spring.redis.controller;

import com.tra22.spring.redis.payload.global.Response;
import com.tra22.spring.redis.service.interf.ICacheService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/caches")
@RequiredArgsConstructor
public class CacheController {
    private final ICacheService cacheService;
    @PostMapping("/{cacheName}")
    public ResponseEntity<Response<?>> clearCacheByCacheName(@PathVariable String cacheName){
        cacheService.evictCacheValueByName(cacheName);
        return new ResponseEntity<>(Response.okWithNoContent(), HttpStatus.NO_CONTENT);
    }
    @PostMapping("/{cacheName}/{key}")
    public ResponseEntity<Response<?>> clearCacheByCacheName(@PathVariable String cacheName, @PathVariable String key){
        cacheService.evictCacheValueByKey(cacheName, key);
        return new ResponseEntity<>(Response.okWithNoContent(), HttpStatus.NO_CONTENT);
    }
    @PostMapping
    public ResponseEntity<Response<?>> clearAllCache(){
        cacheService.evictCacheValues();
        return new ResponseEntity<>(Response.okWithNoContent(), HttpStatus.NO_CONTENT);
    }
    @GetMapping
    public ResponseEntity<Response<?>> getCacheByKey(String cacheName, String key){
        Object obj = cacheService.getCacheValueByKey(cacheName, key);
        return new ResponseEntity<>(Response.ok(obj), HttpStatus.OK);
    }

}

Building the project

You will need:

  • Java JDK 17 or higher
  • Maven 3.5.1 or higher
  • Tomcat 10.1

Clone the project and use Maven to build the server

mvn clean install

Summary

Download the source code for the sample application implementing an API cach with Redis. After this tutorial you will can use redis well than before and how it works. Also redis can apply for alot of purpose and not only cach. 

Comments

Subscribe to our newsletter.

Get updates about new articles, contents, coding tips and tricks.

Weekly articles
Always new release articles every week or weekly released.
No spam
No marketing, share and spam you different notification.
© 2023-2025 Tra21, Inc. All rights reserved.