Русский
Русский
English
Статистика
Реклама

Как Spring Data работает с Redis

Redis (Remote Dictionary Server) - заслужено считается старичком в мире NoSql решений. Этот пост про то, как Spring Data с ним работает. Идея написания данного поста возникла потому, что Redis не совсем похож на привычную базу, он поддерживает типы данных, которые не удобно использовать для хранения объектов(Кеш не в счет) и выполнять поиск по определенным полям. Здесь на примерах я постараюсь описать как с ним работает Spring Data посредством привычного CrudRepository и QueryDSL. Это не пример HowTo, которых множество. Кому интересны внутренности идем дальше.

Примеры будут основаны на простом проекте. Redis поднимается в докер контейнере, spring-boot приложение, которое тоже в контейнере с ним общается. Приложение содержит простую модель, репозиторий, сервис и контроллер. Потрогать все это можно через swagger на localhost:8080.
Кроме команд, которые сервис выполняет к базе я буду приводить еще небольшой псевдо-код, который более понятно описывает происходящее.

Работать мы будем с сущностью Student:

@Data@AllArgsConstructor@NoArgsConstructor@RedisHash("Student")public class Student {    @Id    private String id;    private String name;    private int age;}

Здесь, необходимо уточнить что аннотация @RedisHash("Student") говорит о том, под каким ключом будут агрегироваться все сущности.

Попробуем сохранить первого студента:

curl -X POST "http://localhost:8080/save" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"id\":\"1\",\"name\":\"Stephen\",\"age\":12}"

Выполнились 3 команды:

"DEL" "Student:1""HMSET" "Student:1" "_class" "com.odis.redisserviceweb.model.Student" "id" "1" "name" "Stephen" "age" "12""SADD" "Student" "1"

Мы видим, что первая команда - это "DEL" "Student:1", говорит о том что удали запись с ключом "Student:1". Этот ключ был сформирован с помощью значения аннотации @RedisHash + значение поля помеченного аннотацией @Id.

Далее следует "HMSET" "Student:1" "_class" "com.odis.redisserviceweb.model.Student" "id" "1" "name" "Stephen" "age" "12". Эта команда добавляет значения в хеш с именем "Student:1". На псевдо-коде это будет выглядеть как

Map "Student:1";"Student:1".put("_class", "com.odis.redisserviceweb.model.Student");"Student:1".put("id", "1");"Student:1".put("name", "Stephen");"Student:1".put("age", "12");

Ну и завершающая команда - это "SADD" "Student" "1" - добавляет в сет с именем "Student" значение "1".
В итоге что мы получили? Было создано два объекта в Redis. Первый - хеш с именем "Student:1", второй - сет с именем "Student".

Выполнив команду keys * - дай все ключи (Не выполнять на проде под страхом экзекуции) получим:

127.0.0.1:6379> keys *1) "Student"2) "Student:1"

Типы у этих объектов, как и было ранее описано:

127.0.0.1:6379> type "Student"set127.0.0.1:6379> type "Student:1"hash

Собственно - зачем два объекта? Сейчас все станет на свои места.

В поиске по @Id нет ничего необычного:

curl -X GET "http://localhost:8080/get/1" -H  "accept: */*"

Сформировался ключ "Student:1" и выполнилась команда, которая и вернула искомый объект:

"HGETALL" "Student:1"1) "_class"2) "com.odis.redisserviceweb.model.Student"3) "id"4) "1"5) "name"6) "Stephen"7) "age"8) "12"

А теперь попробуем выполнить поиск всего, что мы сохранили, добавив перед этим еще одного студента:

curl -X POST "http://localhost:8080/save" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"id\":\"2\",\"name\":\"Macaulay\",\"age\":40}"curl -X GET "http://localhost:8080/get" -H  "accept: */*"

Выполнилось 3 команды:

"SMEMBERS" "Student"1) "1"2) "2""HGETALL" "Student:1"1) "_class"2) "com.odis.redisserviceweb.model.Student"3) "id"4) "1"5) "name"6) "Stephen"7) "age"8) "12"127.0.0.1"HGETALL" "Student:2"1) "_class"2) "com.odis.redisserviceweb.model.Student"3) "id"4) "2"5) "name"6) "Macaulay"7) "age"8) "40"

Сначала мы получили список всех ключей и после этого сделали запрос по каждому на получение значения. Именно поэтому было создано несколько объектов - "Student" - хранит все ключи, и по одному объекту на каждого студента с ключом "Student:@Id". Получается, что получение всех студентов имеет сложность O (N) где N - количество объектов в базе.

Удалим студунта:

curl -X DELETE "http://localhost:8080/delete/1" -H  "accept: */*"

Получаем:

"HGETALL" "Student:1"1) "_class"2) "com.odis.redisserviceweb.model.Student"3) "id"4) "1"5) "name"6) "Stephen"7) "age"8) "12""DEL" "Student:1"(integer) 1"SREM" "Student" "1"(integer) 1"SMEMBERS" "Student:1:idx"(empty array)"DEL" "Student:1:idx"(integer) 0

Смотрим, есть ли студент с нужным нам Id Удаляем этот хеш. Удаляем из сета "Student" ключ "1".
А дальше фигурирует объект Student:1:idx. О нем речи не шло ранее. Давайте посмотрим, зачем он необходим. Но для начала попробуем добавить метод в наш репозиторий для поиска студента по имени:

List<Student> findAllByName(String name);

Приложение поднялось сохраняем студента и делаем поиск по имени:

curl -X POST "http://localhost:8080/save" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"id\":\"1\",\"name\":\"Stephen\",\"age\":12}"curl -X GET "http://localhost:8080/get/filter/Stephen" -H  "accept: */*"

В ответе у нас пустой массив, а в логах запросов к Redis видим:

"SINTER" "Student:name:Stephen"(empty array)

Команда "SINTER" - Возвращает элементы сета, полученные в результате пересечения всех данных сетов, в нашем случае передан только один сет - "Student:name:Stephen" но мы о нем ничего не знаем и он не создавался.
Дело в том, что если мы хотим искать по полю, которое не помечено аннотацией @Id, это поле должно быть помечено аннотацией @Indexed и тогда Spring Data сделает дополнительные манипуляции при сохранении студента, т. к. понятие индекс в Redis отсутствует. Пометим поле name этой аннотацией:

@Data@AllArgsConstructor@NoArgsConstructor@RedisHash("Student")public class Student {    @Id    private String id;    @Indexed    private String name;    private int age;}

Теперь сохраним студента в чистую базу:

curl -X POST "http://localhost:8080/save" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"id\":\"1\",\"name\":\"Stephen\",\"age\":12}"

И в логах команд видим:

"DEL" "Student:1""HMSET" "Student:1" "_class" "com.odis.redisserviceweb.model.Student" "id" "1" "name" "Stephen" "age" "12""SADD" "Student" "1""SADD" "Student:name:Stephen" "1""SADD" "Student:1:idx" "Student:name:Stephen"

Первые три команды нам знакомы, но добавились еще две: создали сет "Student:name:Stephen" имя которого состоит из ключа, названия поля, помеченного аннотацией @Indexed и значением этого поля. В этот сет был добавлен Id этого студента. Если у нас появится студент с другим Id и именем Stephen его Id так же будет добавлен в этот сет. И был создан сет, который хранит все ключи индексов которые были созданы для этого объекта. Получилось что-то по типу:

Map "Student:1";"Student:1".put("_class", "com.odis.redisserviceweb.model.Student");"Student:1".put("id", "1");"Student:1".put("name", "Stephen");"Student:1".put("age", "12");Set "Student";"Student".add("1");Set "Student:name:Stephen";"Student:name:Stephen".add("1");Set "Student:1:idx";"Student:1:idx".add("Student:name:Stephen");

Теперь должно работать, Выполним поиск по имени и получим наше значение, в логах команд Redis будет:

"SINTER" "Student:name:Stephen""HGETALL" "Student:1"

Получаем Id студентов, и вытаскиваем их из хеша. Опять же количество операций прямо пропорционально количеству данных.

Так же мы можем искать по нескольким полям для этого в качестве аргумента команды SINTER будут передаваться два объекта. Команда вернет пересекающееся id и выполнит по ним поиск.
В примере есть метод поиска по имени и возрасту.

Как видим, Spring Data достаточно неплохо интерпретирует работу с объектами и индексами в Redis. Данный подход можно перенять и не используя решение Spring.
Недостаток - это то, что поля по которым будет проводится поиск должны быть промаркированы аннотацией @Indexed с самого начала. В противном случае, "индексы" будут созданы только для объектов, которые сохраняются после добавления этой аннотации. И да, я понимаю, что Redis это не лучшее решения для таких нужд, но если в силу определенной ситуации его необходимо будет использовать, то SpringData сумеет это сделать достаточно неплохо.

Источник: habr.com
К списку статей
Опубликовано: 02.09.2020 18:14:51
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Java

Nosql

Redis

Spring data

Spring

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru