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.
Eang Sopheaktra
March 05 2024 10:57 am
Caching is reduce the number of calls made to your endpoint and also improve the latency of requests to your API.
Using cache when your data is not volatile real-time data.
The fully fledged server uses the following:
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>
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()));
}
}
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);
}
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();
}
}
}
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);
}
}
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);
}
}
You will need:
Clone the project and use Maven to build the server
mvn clean install
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.