Кэширование в связке Spring Boot + Redis + PostgreSQL

В этой статье мне хотелось бы показать, как использовать Redis для кэширования при работе с приложением Spring Boot.

Кэширование очень важно для веб-разработки  —  оно помогает быстро получить ответ на запрос.

Приступая к работе

Redis  —  самый популярный инструмент для кэширования. Он широко используется при разработке веб-приложений.

Что такое Redis?

Redis  —  это хранилище структур данных с открытым исходным кодом (лицензия BSD), используемое в качестве базы данных, кэша и посредника сообщений. Redis предоставляет структуры данных, такие как строки, хэши, списки, наборы, отсортированные наборы с запросами по диапазону, растровые изображения, гиперлоги, геопространственные индексы и потоки.

Для этой статьи я создам простую демо-версию. Она включает в себя класс Customer. На уровне контроллера есть методы getAllCustomers(), getCustomerById(). Эти методы кэшируемые. Если отправлять запросы для этих методов, ожидание может занять до трех секунд, если у Redis в памяти нет связанных данных. 

Сценарий примерно такой. Начнем.

Зависимости Maven

Прежде всего, нужно добавить соответствующие зависимости в файл pom.xml

pom.xml:

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

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

Конфигурация Redis

Создадим конфигурационный файл для Redis-конфигурации, как показано ниже, а также компоненты redisCacheTemplate и CacheManager.

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class RedisConfig {

@Autowired
private CacheManager cacheManager;

@Value("${spring.redis.host}")
private String redisHost;

@Value("${spring.redis.port}")
private int redisPort;

@Bean
public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration = config
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration)
.build();
return redisCacheManager;
}

}

Аннотации кэша Spring

Я воспользовался аннотациям кэша Spring. Объясню, что это такое.

  • @cacheable  —  запускает заполнение кэша;
  • @CacheEvict  —  запускает удаление кэша;
  • @CachePut  —  обновляет кэш, не вмешиваясь в выполнение метода;
  • @Caching  —  перегруппирует несколько операций кэша, которые будут применены к методу;
  • @CacheConfig  —  использует некоторые общие настройки, связанные с кэшем, на уровне класса.

Уровень обслуживания

Я создал уровень обслуживания для клиента. Для кэширования использованы аннотации Spring. Когда вы получите всех клиентов, то получите все данные из базы данных, если у Redis нет связанных данных. Метод waitSomeSome() написан для моделирования. При добавлении нового клиента Customer или его обновлении, все связанные данные в Redis будут удалены.

@Service
@CacheConfig(cacheNames = "customerCache")
public class CustomerServiceImpl implements CustomerService {

@Autowired
private CustomerRepository customerRepository;

@Cacheable(cacheNames = "customers")
@Override
public List<Customer> getAll() {
waitSomeTime();
return this.customerRepository.findAll();
}

@CacheEvict(cacheNames = "customers", allEntries = true)
@Override
public Customer add(Customer customer) {
return this.customerRepository.save(customer);
}

@CacheEvict(cacheNames = "customers", allEntries = true)
@Override
public Customer update(Customer customer) {
Optional<Customer> optCustomer = this.customerRepository.findById(customer.getId());
if (!optCustomer.isPresent())
return null;
Customer repCustomer = optCustomer.get();
repCustomer.setName(customer.getName());
repCustomer.setContactName(customer.getContactName());
repCustomer.setAddress(customer.getAddress());
repCustomer.setCity(customer.getCity());
repCustomer.setPostalCode(customer.getPostalCode());
repCustomer.setCountry(customer.getCountry());
return this.customerRepository.save(repCustomer);
}

@Caching(evict = { @CacheEvict(cacheNames = "customer", key = "#id"),
@CacheEvict(cacheNames = "customers", allEntries = true) })
@Override
public void delete(long id) {
this.customerRepository.deleteById(id);
}

@Cacheable(cacheNames = "customer", key = "#id", unless = "#result == null")
@Override
public Customer getCustomerById(long id) {
waitSomeTime();
return this.customerRepository.findById(id).orElse(null);
}

private void waitSomeTime() {
System.out.println("Long Wait Begin");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Long Wait End");
}

Docker и Docker Compose

Для настройки приложения создадим файлы Dockerfile и docker-compose.yml. В качестве базы данных СУБД воспользуемся postgresql. В файл docker-compose.yml добавим Redis с тэгом cache. Docker compose —  очень полезный инструмент для запуска сразу нескольких контейнеров.

Dockerfile:

openjdk:8
ADD ./target/spring-boot-redis-cache-0.0.1-SNAPSHOT.jar /usr/src/spring-boot-redis-cache-0.0.1-SNAPSHOT.jar
WORKDIR usr/src
ENTRYPOINT ["java","-jar", "spring-boot-redis-cache-0.0.1-SNAPSHOT.jar"]

docker-compose.yml:

version: '3'

services:
db:
image: "postgres"
ports:
- "5432:5432"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ekoloji
cache:
image: "redis"
ports:
- "6379:6379"
environment:
- ALLOW_EMPTY_PASSWORD=yes
- REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db/postgres
SPRING_DATASOURCE_USERNAME: postgres
SPRING_DATASOURCE_PASSWORD: ekoloji
SPRING_REDIS_HOST: cache
SPRING_REDIS_PORT: 6379
depends_on:
- db
- cache

Сборка и запуск

При создании и запуске этого приложения вам необходимо выполнить некоторые команды из командной строки, такие как mvn и docker. Сначала приложение упаковывается в jar-формат. После этого создается файл docker-compose. И, наконец, вам просто нужно up-нуть Docker-файл.

Когда вы проследуете по этому пути, ваше приложение будет успешно запущено. Просто перейдите на http://localhost:8080 или http://localhost:8080/swagger-ui.html. По второй ссылке можно будет посмотреть эндпоинты проекта, а также протестировать их с помощью инструмента Swagger.

Создание Java jar:

$ mvn clean install -DskipTests

Создание и запуск Docker Compose:

$ docker-compose build --no-cache
$ docker-compose up --force-recreate

Демонстрация

Я подготовил демо-видео о том, как запустить и протестировать это приложение.

Заключение

Благодаря этому краткому руководству мы настроили приложение Spring Boot для взаимодействия с Redis и PostgreSQL через Docker.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Erkan Güzeler: “Spring Boot + Redis + PostgreSQL Caching”

Предыдущая статьяКарьерные трудности UX/UI дизайнера
Следующая статьяШоу должно продолжаться: обеспечение безопасности Netflix Studios с масштабированием