Статьи
YouTube-канал

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

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

30 июля 2022

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

Содержание

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

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

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

Создадим стандартное приложение Spring Boot. Это удобно делать через Spring Initializr. Если вы используете gradle, то в файле build.gradle должны быть две зависимости:

implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-web'

Если же вы используете maven, то зависимости будут в файле 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().

Метод 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) {
    // запись будет удалена из кеша
}

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

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

@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";
    }
}

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

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

Для активации механизма кеширования не забудьте добавить аннотацию @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-запрос. И затем ещё раз GET-запрос - он опять ответит с задержкой. То есть после DELETE записи в кеше не было.

Выводы

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

Облако тэгов

Kotlin, Java, Java 16, Java 11, Java 10, Java 9, Java 8, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.

Последние статьи


Комментарии

16.04.2022 09:30 Михаил

Спасибо!)

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

×

devmark.ru