Redis и Spring Data с самого начала. Часть 2
В предыдущей части мы рассмотрели NoSQL базы данных, в частности Key-Value NoSQL БД, и обсудили одну из самых популярных NoSQL БД — Redis. В этой части посмотрим на общие возможности Spring Data Key Value, а также изучим специфичные плюшки для работы именно с Redis.
Spring Data
Для начала познакомимся с родительским проектом – Spring Data создан для подключения к различным БД на уровне бизнес-сущностей. Spring Data вводит понятие репозитория и предоставляет некоторые общие аннотации, которые по разному используются при подключении к специфичным БД и технологиям. Эти возможности находятся в проекте Spring Data Commons.
Spring Data также включает другие проекты: • Spring Data JDBC – реализует репозитории для подключения к реляционной БД с помощью JDBC. Не путать с низкоуровневым Spring JDBC, просто упрощающим подключение по JDBC; • Spring Data JPA – позволяет подключаться к реляционным БД с помощью JPA и выбрав Hibernate или EclipseLink в качестве JPA Provider-а; • Spring Data R2DBC – специальный модуль для подключения к реляционным БД с асинхронным драйвером (H2, PostgreSQL, MS SQL) на реактивной основе с помощью технологии R2DBC; • Spring Data MongoDB – позволяет подключаться к документ-ориентированной MongoDB; • Spring Data REST – совсем «Дзен», позволяет элементарно создать REST-интерфейс репозитория, основана на принципах HATEOAS; • Spring Data Key Value – корневой проект для подключения к Key-Value NoSQL-базам данных. Также содержит дефолтную реализацию Key-Value-хранилища на основе HashMap (да-да!); • Spring Data Redis – соответственно, для подключения к Redis. Использует абсолютно такие же подходы, что и Spring Data Key Value; • и многие другие, включая поддерживаемые сообществом.
Несмотря на большое количество проектов и внешнюю сложность, разрабатывать приложения, использующие Spring Data, – огромное удовольствие. Рассмотрим общие возможности Spring Data Key Value.
Spring Data Redis Repositories
Разрабатывать приложения со Spring Data будем, естественно, через Spring Boot. Представим, что мы сгенерировали Spring Boot-проект (например, с помощью Spring Initializr) и добавим туда зависимость стартера:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
В лучших традициях Spring Boot добавление одной только этой зависимости включает/подключает следующие технологии и библиотеки:
• Spring Data Redis – ну, ради этого, собственно, и написан стартер. Вводит различные
Напишем наш объект, который будет храниться в Redis (будем считать, что весь бойлерплейт написан хитрым lombok, работающем на комментариях):
@RedisHash("employee") public class Person { @Id private String id; private String firstName; private String lastName; // конструктор, геттеры и сеттеры }
Это код очень похож на JPA Entity, но имеет некоторые отличия. Во-первых, аннотация
Другая аннотация –
Ставить эту аннотацию не обязательно – считается, что она стоит по умолчанию с именем класса, т. е.
Итак, у нас есть объект, теперь нам необходимо написать репозиторий (DAO на бизнес-уровне). Один из способов использовать Spring Data – написать репозиторий данных объектов. Сделаем это:
public interface PersonRepository extends KeyValueRepository<Person, String> { }
Как ни странно, на этом всё! Больше никакого кода писать не нужно. Вся фишка Spring Data Repositories заключается в написании интерфейса, реализацию которого делает за вас Spring Data. KeyValueRepository – интерфейс из Spring Data Key Value, который в частности наследуется от PagingAndSortingRepositry, а он в свою очередь от CrudRepository, который является частью уже Spring Data.
В принципе, можно наследовать интерфейсы репозиториев от PagingAndSortingRepository или CrudRepository (сам KeyValueRepository не добавляет ни одного метода), но, к сожалению, сканирование репозиториев в
Несложно догадаться, что первым параметром generic-а является сущность, а вторым – тип идентификатора. Да, всё правильно! String означает строчное представление UUID. Да, они у нас будут случайно сгенерированы. При этом функционал PagingAndSortingRepository (пейджинация и сортировка) будет работать. KeyValueRepository, c учётом всей иерархии, содержит массу общих методов вроде save, findAll, count, delete и др., которых более чем достаточно для простого использования.
Напишем код сохранения в Redis нашей сущности прямо в main, оставив правила приличия тем, кто будет разрабатывать приложения:
@SpringBootApplication public class Main { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(Main.class, args); PersonRepository repository = context.getBean(PersonRepository.class); repository.save(new Person("Ivan", "Ivanov")); repository.save(new Person("Cidre", "Sidorov")); } }
А теперь начинается самое интересное: давайте посмотрим в Redis, что у нас сохранилось:
127.0.0.1:6379> keys * 1) "employee:1b06b207-a453-4250-89ac-9c4f83d437d9" 2) "employee" 3) "employee:9b09223d-b9c1-4fd4-abd8-c0f52ed432b0"
Вот и разберёмся, как реализуется
127.0.0.1:6379> type employee set 127.0.0.1:6379> smembers employee 1) "9b09223d-b9c1-4fd4-abd8-c0f52ed432b0" 2) "1b06b207-a453-4250-89ac-9c4f83d437d9"
Да, здесь лежат все ключи всех Person-ов. Теперь понятно, как можно реализовать, например, метод count-репозитория. А функционал
127.0.0.1:6379> type "employee:1b06b207-a453-4250-89ac-9c4f83d437d9" hash 127.0.0.1:6379> hgetall "employee:1b06b207-a453-4250-89ac-9c4f83d437d9" 1) "_class" 2) "ru.otus.springdataredisexample.model.Person" 3) "id" 4) "1b06b207-a453-4250-89ac-9c4f83d437d9" 5) "firstName" 6) "Cidre" 7) "lastName" 8) "Sidorov"
Как и следовало ожидать – это уже знакомый нам hash. Создадим более сложную сущность, и сериализация станет хитрее. Введём другой класс – Address. Обратите внимание, этот класс не имеет аннотацию
public class Address { private String city; private String street; private String number; // конструктор, геттеры и сеттеры }
Добавим ссылку на данный класс:
@RedisHash("employee") public class Person { @Id private String id; private String firstName; private String lastName; private Address address; // конструктор, геттеры и сеттеры }
Создадим и запишем соответствующий объект:
Address omsk = new Address("Omsk", "Lenina", "2"); repository.save(new Person("Cidre", "Sidorov", omsk));
А теперь его прочитаем:
127.0.0.1:6379> hgetall employee:8e197d23-e2e3-4927-b952-6f4840282b97 1) "_class" 2) "ru.otus.springdataredisexample.model.Person" 3) "id" 4) "8e197d23-e2e3-4927-b952-6f4840282b97" 5) "firstName" 6) "Cidre" 7) "lastName" 8) "Sidorov" 9) "address.city" 10) "Omsk" 11) "address.street" 12) "Lenina" 13) "address.number" 14) "2"
Подобный вид сериализации внутренних классов называется Flat Hash Mapping. Его, кстати, можно настроить вплоть до хранения строки JSON. Делается это различными способами.
К сожалению, ссылки хэшей друг-на друга – аналог
Ну и напоследок стоит сказать про дополнительные методы, которые можно добавлять в репозиторий:
public interface PersonRepository extends KeyValueRepository<Person, String> { List<Person> findByFirstName(String firstName); List<Person> findByLastName(String lastName); }
Да, традиционно для Spring Data реализации этих методов напишет за вас Spring Data. И встаёт логичный вопрос – а как будет реализован поиск, если нет никаких индексов? Да, всё верно – будет реализован полным поиском со сложностью o(n). И, тем не менее, есть возможность организовать с помощью структур Redis подобный индекс:
@RedisHash("employee") public class Person { @Id private String id; @Indexed private String firstName; @Indexed private String lastName; private Address address; // конструктор, геттеры и сеттеры }
Посмотрим, что сохранилось в БД:
127.0.0.1:6379> keys * 1) "employee:firstName:Cidre" 2) "employee" 3) "employee:989606f3-13fe-43e7-a652-aabf15bdbb93" 4) "employee:lastName:Sidorov" 5) "employee:989606f3-13fe-43e7-a652-aabf15bdbb93:idx" 127.0.0.1:6379> type employee:firstName:Cidre set 127.0.0.1:6379> smembers employee:firstName:Cidre 1) "989606f3-13fe-43e7-a652-aabf15bdbb93" 127.0.0.1:6379> type employee:989606f3-13fe-43e7-a652-aabf15bdbb93:idx set 127.0.0.1:6379> smembers employee:989606f3-13fe-43e7-a652-aabf15bdbb93:idx 1) "employee:firstName:Cidre" 2) "employee:lastName:Sidorov"
Ну и теперь понятно, что собой представляют индексы.
На этом изучение Spring Data Redis-репозиториев закончено, предлагаю рассмотреть ещё одну важную возможность Spring Data Redis в следующей части.
Есть вопросы? Напишите в комментариях!