Статьи Утилиты Telegram YouTube RuTube Отзывы

Кеширование в Spring Boot

Видеогайд Исходники

19 ноября 2024

Тэги: Java, maven, rest, Spring Boot.

Содержание

  1. Подключение зависимостей
  2. Модель данных
  3. Сервисный слой
  4. REST-контроллер
  5. Включение кеширования
  6. Выводы

Spring Boot поддерживает простой механизм кеширования данных. Рассмотрим его на примере, исходники которого доступны на github.

Подключение зависимостей

Создадим стандартное приложение Spring Boot. Это удобно делать через Spring Initializr.

Добавим в pom.xml две зависимости:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Зависимость spring-boot-starter-web – это базовая функциональность нашего веб-приложения, в том числе поддержка rest-контроллеров. Зависимость spring-boot-starter-cache добавляет возможность кеширования.

Модель данных

Предположим, что мы хотим кешировать некие записи, представленные классом MyRecord. Очевидно, что в последних версиях Java это должен быть не просто класс, а record-класс. То есть класс, предназначенный для хранения данных. Все поля доступа и служебные методы для него компилятор генерирует автоматически.

public record MyRecord(int id, LocalTime creationTime) {
}

MyRecord содержит всего лишь два поля: числовой идентификатор и время создания для наглядной демонстрации изменения состояния кеша.

Сервисный слой

Предположим, что извлечение этих данных без кеширования является дорогой по времени операцией. Для этого создадим сервис, который будет имитировать «тяжёлое» обращение к хранилищу с данными. Именно это обращение мы будем затем кешировать.

@Service
public class HighloadService {
}

В реальных проектах обращение к БД, либо к удалённому сервису может быть очень долгим, поэтому имеет смысл кешировать редко изменяющиеся объекты.

Далее добавляем в сервис четыре метода: getOrCreateRecord(), createOrUpdateRecord(), deleteRecord(), deleteAllRecords().

Метод getOrCreateRecord() возвращает объект по его id. Имитация «тяжёлого» обращения к БД делается с помощью метода TimeUnit.SECONDS.sleep(3), который приостанавливает выполнение потока на 3 секунды. Затем просто создаём новый объект и возвращаем его как результат. Аннотация @Cacheable говорит, что возвращаемый результат необходимо положить в кеш, если его там нет. А если он там есть, то просто вернуть значение из кеша. Параметр cacheNames указывает на имя кеша, а key указывает параметр метода, по значению которого проверять наличие элемента в кеше. Обратите внимание на символ «#», стоящий перед именем параметра.

@Cacheable(cacheNames = "recordsCache", key = "#recordId")
public MyRecord getOrCreateRecord(int recordId) {
    try {
        TimeUnit.SECONDS.sleep(3);
        // запись будет создана в кеше только 1 раз
        return new MyRecord(recordId, LocalTime.now());
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

При первом обращении к методу вы увидите задержку в 3 секунды, а затем получите в ответ созданный объект. При последующих обращениях с тем же id результат будет возвращаться мгновенно, т.к. запись будет браться из кеша и дата создания объекта меняться не будет.

Метод createOrUpdateRecord() создаёт новый объект MyRecord и возвращает его в качестве результата. Аннотация @CachePut принудительно помещает объект, возвращаемый данным методом, в кеш. Если в кеше по данному ключу уже был объект, он будет перезаписан. Имя кеша, аналогично предыдущей аннотации, указывается в параметре cacheNames, а имя параметра метода, по значению которого следует искать запись в кеше – через параметр key.

@CachePut(cacheNames = "recordsCache", key = "#recordId")
public MyRecord createOrUpdateRecord(int recordId) {
    // запись будет создаваться (обновляться) в кеше каждый раз
    return new MyRecord(recordId, LocalTime.now());
}

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

Метод deleteRecord() сам ничего не делает. Однако аннотация @CacheEvict удаляет запись из кеша по указанному ключу recordId. После выполнения данного метода в кеше будет удаляться одна запись.

@CacheEvict(cacheNames = "recordsCache", key = "#recordId")
public void deleteRecord(int recordId) {
    // запись будет удалена из кеша
}

Однако иногда бывает полезно сбросить весь кеш целиком, т.е. удалить разом все записи. Для этого также будем использовать аннотацию @CacheEvict с флагом allEntries.

@CacheEvict(cacheNames = "recordsCache", allEntries = true)
public void deleteAllRecords() {
    // очищаем кеш полностью
}

REST-контроллер

Теперь создадим простой RestController, чтобы можно было вызывать наши методы.

@RestController
public class HighloadController {

    private final HighloadService highloadService;

    public HighloadController(HighloadService highloadService) {
        this.highloadService = highloadService;
    }

    @GetMapping("/{id}")
    public MyRecord getOrCreateRecord(@PathVariable int id) {
        return highloadService.getOrCreateRecord(id);
    }

    @PutMapping("/{id}")
    public MyRecord createOrUpdateRecord(@PathVariable int id) {
        return highloadService.createOrUpdateRecord(id);
    }

    @DeleteMapping("/{id}")
    public String deleteRecord(@PathVariable int id) {
        highloadService.deleteRecord(id);
        return "Record deleted";
    }

    @DeleteMapping
    public String deleteAllRecords() {
        highloadService.deleteAllRecords();
        return "All records deleted";
    }
}

При запуске приложения данный контроллер по умолчанию будет доступен по адресу 127.0.0.1:8080. Метод getOrCreateRecord вызывается с помощью GET-запроса по адресу 127.0.0.1:8080/{id}, где id – номер нашей записи. Аналогично метод createOrUpdateRecord вызывается PUT-запросом по тому же адресу с указанием id записи. Метод deleteRecord – DELETE-запросом с указанием id. А метод deleteAllRecords – тоже DELETE-запросом, но без указания id.

Включение кеширования

Для активации механизма кеширования не забудьте добавить аннотацию @EnableCaching в основной класс нашего приложения, туда, где находится стандартный метод main() и аннотация @SpringBootApplication.

@EnableCaching // включаем механизм кеширования
@SpringBootApplication
public class CachingExampleApplication {

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

Запускаем приложение и делаем GET-запрос на 127.0.0.1:8080/1. Мы наблюдаем долгий ответ, т.е. для id = 1 объекта в кеше ещё нет. Повторные запросы выполняются мгновенно, а ответ возвращается один и тот же. Далее делаем PUT-запрос на тот же адрес. Объект будет создан и принудительно помещён в кеш. Ещё раз выполняем тот же GET-запрос и наблюдаем быстрый ответ, который вернёт нам сущность, созданную методом PUT. Теперь удалим эту запись, выполнив DELETE-запрос с указанием этого id. И затем ещё раз GET-запрос – он опять ответит с задержкой. То есть после DELETE записи в кеше не было.

Для проверки сброса всего кеша, можно выполнить несколько GET-запросов с разным id. У нас в кеш будет помещено несколько записей. Затем вызовем DELETE без id – и это приведёт к удалению всех записей из кеша. Ещё раз вызовем GET-запрос с указанием ранее созданного id – и снова будем наблюдать задержку в 3 секунды. То есть кеш был очищен корректно.

Выводы

  • Кеширование записей всегда происходит по указанному id. То есть по одной записи. Spring не предлагает какого-либо api для получения сразу всех закешированных записей. Также нельзя добавить сразу несколько записей в кеш.
  • Метод с аннотацией @Cacheable не выполняется, если по указанному id в кеше уже есть запись. Она будет извлечена из кеша и возвращена в качестве результата работы метода.
  • Метод с аннотацией @CachePut можно вызывать любое количество раз для одного и того же id. И каждый раз объект в кеше будет обновляться. При этом важно, чтобы метод, помеченный этой аннотацией, возвращал обновлённый объект.


Комментарии

16.04.2022 09:30 Михаил

Спасибо!)

19.11.2024 21:57 devmark

Обновил статью про кеширование запросов в Spring. Также перевёл пример на github на Spring Boot 3 и Java 21.

Добавить комментарий

×

devmark.ru