[Java, NoSQL] Как Spring Data работает с Redis
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
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"
set
127.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
===========
Похожие новости:
- [IT-инфраструктура, NoSQL, Серверное администрирование] Дружим ELK и Exchange. Часть 1
- [JavaScript, TypeScript, Визуализация данных] How to display a gazillion of metrics and keep your sanity
- [Canvas, JavaScript] Sprite Matrix — модуль для нарезки спрайтов, редактирования и сохранения спрайт-листов
- [Настройка Linux, Разработка веб-сайтов, PHP, JavaScript, Серверное администрирование] Перенос почты между серверами через интерфейс пользователя посредством IMAPSync
- [Тестирование IT-систем, Java] Home видео для Selenium aka WebDriver. Или чем записать экран, если у вас есть java, поломанные тесты и немного времени
- [Java] Скринкаст: простейший плагин для IntelliJ IDEA
- [Алгоритмы, Высокая производительность, Математика, Программирование] Динамическая балансировка нагрузки в pull-схеме
- [Big Data, Java, Scala] Big Data Tools EAP 10: SSH-туннели, фильтрация приложений, пользовательские модули и многое другое
- [JavaScript, ReactJS, TypeScript] Пишем свой CLI генерации React компонент, а может быть не только компонент, а может не только React
- [JavaScript, Программирование, Разработка веб-сайтов] JavaScript: область видимости простыми словами (перевод)
Теги для поиска: #_java, #_nosql, #_java, #_redis, #_spring_data, #_spring, #_java, #_nosql
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:46
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
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; } 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" 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"); В итоге что мы получили? Было создано два объекта в Redis. Первый - хеш с именем "Student:1", второй - сет с именем "Student".Выполнив команду keys * - дай все ключи (Не выполнять на проде под страхом экзекуции) получим: 127.0.0.1:6379> keys *
1) "Student" 2) "Student:1" 127.0.0.1:6379> type "Student"
set 127.0.0.1:6379> type "Student:1" hash curl -X GET "http://localhost:8080/get/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" 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: */*" "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" 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 А дальше фигурирует объект 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: */*" "SINTER" "Student:name:Stephen"
(empty array) Дело в том, что если мы хотим искать по полю, которое не помечено аннотацией @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" 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"); "SINTER" "Student:name:Stephen"
"HGETALL" "Student:1" В примере есть метод поиска по имени и возрасту.Как видим, Spring Data достаточно неплохо интерпретирует работу с объектами и индексами в Redis. Данный подход можно перенять и не используя решение Spring. Недостаток - это то, что поля по которым будет проводится поиск должны быть промаркированы аннотацией @Indexed с самого начала. В противном случае, "индексы" будут созданы только для объектов, которые сохраняются после добавления этой аннотации. И да, я понимаю, что Redis это не лучшее решения для таких нужд, но если в силу определенной ситуации его необходимо будет использовать, то SpringData сумеет это сделать достаточно неплохо. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 22:46
Часовой пояс: UTC + 5