0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>В<a>предыдущей части</a>мы рассмотрели NoSQL базы данных, в частности Key-Value NoSQL БД, и обсудили одну из самых популярных NoSQL БД - Redis. В этой части посмотрим на общие возможности Spring Data Key Value, а также изучим специфичные плюшки для работы именно с Redis.</p>
1
<p>В<a>предыдущей части</a>мы рассмотрели NoSQL базы данных, в частности Key-Value NoSQL БД, и обсудили одну из самых популярных NoSQL БД - Redis. В этой части посмотрим на общие возможности Spring Data Key Value, а также изучим специфичные плюшки для работы именно с Redis.</p>
2
<h2>Spring Data</h2>
2
<h2>Spring Data</h2>
3
<p>Для начала познакомимся с родительским проектом - Spring Data создан для подключения к различным БД на уровне бизнес-сущностей. Spring Data вводит понятие репозитория и предоставляет некоторые общие аннотации, которые по разному используются при подключении к специфичным БД и технологиям. Эти возможности находятся в проекте<strong>Spring Data Commons</strong>.</p>
3
<p>Для начала познакомимся с родительским проектом - Spring Data создан для подключения к различным БД на уровне бизнес-сущностей. Spring Data вводит понятие репозитория и предоставляет некоторые общие аннотации, которые по разному используются при подключении к специфичным БД и технологиям. Эти возможности находятся в проекте<strong>Spring Data Commons</strong>.</p>
4
<p>Spring Data также включает другие проекты: •<strong>Spring Data JDBC</strong>- реализует репозитории для подключения к реляционной БД с помощью JDBC. Не путать с низкоуровневым Spring JDBC, просто упрощающим подключение по JDBC; •<strong>Spring Data JPA</strong>- позволяет подключаться к реляционным БД с помощью JPA и выбрав Hibernate или EclipseLink в качестве JPA Provider-а; •<strong>Spring Data R2DBC</strong>- специальный модуль для подключения к реляционным БД с асинхронным драйвером (H2, PostgreSQL, MS SQL) на реактивной основе с помощью технологии R2DBC; •<strong>Spring Data MongoDB</strong>- позволяет подключаться к документ-ориентированной MongoDB; •<strong>Spring Data REST</strong>- совсем "Дзен", позволяет элементарно создать REST-интерфейс репозитория, основана на принципах HATEOAS; •<strong>Spring Data Key Value</strong>- корневой проект для подключения к Key-Value NoSQL-базам данных. Также содержит дефолтную реализацию Key-Value-хранилища на основе HashMap (да-да!); •<strong>Spring Data Redis</strong>- соответственно, для подключения к Redis. Использует абсолютно такие же подходы, что и Spring Data Key Value; • и многие другие, включая поддерживаемые сообществом.</p>
4
<p>Spring Data также включает другие проекты: •<strong>Spring Data JDBC</strong>- реализует репозитории для подключения к реляционной БД с помощью JDBC. Не путать с низкоуровневым Spring JDBC, просто упрощающим подключение по JDBC; •<strong>Spring Data JPA</strong>- позволяет подключаться к реляционным БД с помощью JPA и выбрав Hibernate или EclipseLink в качестве JPA Provider-а; •<strong>Spring Data R2DBC</strong>- специальный модуль для подключения к реляционным БД с асинхронным драйвером (H2, PostgreSQL, MS SQL) на реактивной основе с помощью технологии R2DBC; •<strong>Spring Data MongoDB</strong>- позволяет подключаться к документ-ориентированной MongoDB; •<strong>Spring Data REST</strong>- совсем "Дзен", позволяет элементарно создать REST-интерфейс репозитория, основана на принципах HATEOAS; •<strong>Spring Data Key Value</strong>- корневой проект для подключения к Key-Value NoSQL-базам данных. Также содержит дефолтную реализацию Key-Value-хранилища на основе HashMap (да-да!); •<strong>Spring Data Redis</strong>- соответственно, для подключения к Redis. Использует абсолютно такие же подходы, что и Spring Data Key Value; • и многие другие, включая поддерживаемые сообществом.</p>
5
<p>Несмотря на большое количество проектов и внешнюю сложность, разрабатывать приложения, использующие Spring Data, - огромное удовольствие. Рассмотрим общие возможности Spring Data Key Value.</p>
5
<p>Несмотря на большое количество проектов и внешнюю сложность, разрабатывать приложения, использующие Spring Data, - огромное удовольствие. Рассмотрим общие возможности Spring Data Key Value.</p>
6
<h2>Spring Data Redis Repositories</h2>
6
<h2>Spring Data Redis Repositories</h2>
7
<p>Разрабатывать приложения со Spring Data будем, естественно, через<strong>Spring Boot</strong>. Представим, что мы сгенерировали Spring Boot-проект (например, с помощью<a>Spring Initializr</a>) и добавим туда зависимость стартера:</p>
7
<p>Разрабатывать приложения со Spring Data будем, естественно, через<strong>Spring Boot</strong>. Представим, что мы сгенерировали Spring Boot-проект (например, с помощью<a>Spring Initializr</a>) и добавим туда зависимость стартера:</p>
8
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency><p>В лучших традициях Spring Boot добавление одной только этой зависимости включает/подключает следующие технологии и библиотеки: •<strong>Spring Data Redis</strong>- ну, ради этого, собственно, и написан стартер. Вводит различные *Operations, а также аннотацию @RedisHash; •<strong>Spring Data Key Value</strong>- про него мы уже сказали, он вводит абстракцию KeyValueRepository; •<strong>Spring Data</strong>- вводит собственную аннотацию @Id; •<strong><a>Lettuce</a></strong>- специальный проект Pivotal для непосредственного подключения к Redis. Использует Netty и Project Reactor (RxJava - опциональна, по желанию). •<strong>Spring Tx</strong>для управления транзакциями. Да, всё так - можно делать транзакции на Redis-е. Но, к сожалению, работа с транзакциями должна быть выключена при работе с репозиториями.</p>
8
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency><p>В лучших традициях Spring Boot добавление одной только этой зависимости включает/подключает следующие технологии и библиотеки: •<strong>Spring Data Redis</strong>- ну, ради этого, собственно, и написан стартер. Вводит различные *Operations, а также аннотацию @RedisHash; •<strong>Spring Data Key Value</strong>- про него мы уже сказали, он вводит абстракцию KeyValueRepository; •<strong>Spring Data</strong>- вводит собственную аннотацию @Id; •<strong><a>Lettuce</a></strong>- специальный проект Pivotal для непосредственного подключения к Redis. Использует Netty и Project Reactor (RxJava - опциональна, по желанию). •<strong>Spring Tx</strong>для управления транзакциями. Да, всё так - можно делать транзакции на Redis-е. Но, к сожалению, работа с транзакциями должна быть выключена при работе с репозиториями.</p>
9
<p>Напишем наш объект, который будет храниться в Redis (будем считать, что весь бойлерплейт написан хитрым lombok, работающем на комментариях):</p>
9
<p>Напишем наш объект, который будет храниться в Redis (будем считать, что весь бойлерплейт написан хитрым lombok, работающем на комментариях):</p>
10
@RedisHash("employee") public class Person { @Id private String id; private String firstName; private String lastName; // конструктор, геттеры и сеттеры }<p>Это код очень похож на JPA Entity, но имеет некоторые отличия. Во-первых, аннотация @Id - это совсем не JPA-аннотация, а собственная аннотация Spring Data, служит для поддержания абстракций Spring Data.</p>
10
@RedisHash("employee") public class Person { @Id private String id; private String firstName; private String lastName; // конструктор, геттеры и сеттеры }<p>Это код очень похож на JPA Entity, но имеет некоторые отличия. Во-первых, аннотация @Id - это совсем не JPA-аннотация, а собственная аннотация Spring Data, служит для поддержания абстракций Spring Data.</p>
11
<p>Другая аннотация - @RedisHash, это уже аннотация из Spring Data Redis, синоним аннотации @KeySpace в Spring Data Key Value. C помощью неё можно задать пространство ключей -<strong>key space</strong>. Можно считать, что это что-то вроде таблицы с sequence в терминах SQL или коллекции в терминах MongoDB. Что будет представлять key space в Redis, мы узнаем совсем скоро.</p>
11
<p>Другая аннотация - @RedisHash, это уже аннотация из Spring Data Redis, синоним аннотации @KeySpace в Spring Data Key Value. C помощью неё можно задать пространство ключей -<strong>key space</strong>. Можно считать, что это что-то вроде таблицы с sequence в терминах SQL или коллекции в терминах MongoDB. Что будет представлять key space в Redis, мы узнаем совсем скоро.</p>
12
<p>Ставить эту аннотацию не обязательно - считается, что она стоит по умолчанию с именем класса, т. е. @RedisHash("ru.full.pakcage.name.Person").</p>
12
<p>Ставить эту аннотацию не обязательно - считается, что она стоит по умолчанию с именем класса, т. е. @RedisHash("ru.full.pakcage.name.Person").</p>
13
<p>Итак, у нас есть объект, теперь нам необходимо написать репозиторий (DAO на бизнес-уровне). Один из способов использовать Spring Data - написать репозиторий данных объектов. Сделаем это:</p>
13
<p>Итак, у нас есть объект, теперь нам необходимо написать репозиторий (DAO на бизнес-уровне). Один из способов использовать Spring Data - написать репозиторий данных объектов. Сделаем это:</p>
14
public interface PersonRepository extends KeyValueRepository<Person, String> { }<p>Как ни странно, на этом всё! Больше никакого кода писать не нужно. Вся фишка<strong>Spring Data Repositories</strong>заключается в написании интерфейса, реализацию которого делает за вас Spring Data. KeyValueRepository - интерфейс из Spring Data Key Value, который в частности наследуется от PagingAndSortingRepositry, а он в свою очередь от CrudRepository, который является частью уже Spring Data.</p>
14
public interface PersonRepository extends KeyValueRepository<Person, String> { }<p>Как ни странно, на этом всё! Больше никакого кода писать не нужно. Вся фишка<strong>Spring Data Repositories</strong>заключается в написании интерфейса, реализацию которого делает за вас Spring Data. KeyValueRepository - интерфейс из Spring Data Key Value, который в частности наследуется от PagingAndSortingRepositry, а он в свою очередь от CrudRepository, который является частью уже Spring Data.</p>
15
<p>В принципе, можно наследовать интерфейсы репозиториев от PagingAndSortingRepository или CrudRepository (сам KeyValueRepository не добавляет ни одного метода), но, к сожалению, сканирование репозиториев в spring-boot-starter-data-redis настроено только на KeyValueRepository.</p>
15
<p>В принципе, можно наследовать интерфейсы репозиториев от PagingAndSortingRepository или CrudRepository (сам KeyValueRepository не добавляет ни одного метода), но, к сожалению, сканирование репозиториев в spring-boot-starter-data-redis настроено только на KeyValueRepository.</p>
16
<p>Несложно догадаться, что первым параметром generic-а является сущность, а вторым - тип идентификатора. Да, всё правильно! String означает строчное представление UUID. Да, они у нас будут случайно сгенерированы. При этом функционал PagingAndSortingRepository (пейджинация и сортировка) будет работать. KeyValueRepository, c учётом всей иерархии, содержит массу общих методов вроде<strong>save, findAll, count, delete</strong>и др., которых более чем достаточно для простого использования.</p>
16
<p>Несложно догадаться, что первым параметром generic-а является сущность, а вторым - тип идентификатора. Да, всё правильно! String означает строчное представление UUID. Да, они у нас будут случайно сгенерированы. При этом функционал PagingAndSortingRepository (пейджинация и сортировка) будет работать. KeyValueRepository, c учётом всей иерархии, содержит массу общих методов вроде<strong>save, findAll, count, delete</strong>и др., которых более чем достаточно для простого использования.</p>
17
<p>Напишем код сохранения в Redis нашей сущности прямо в main, оставив правила приличия тем, кто будет разрабатывать приложения:</p>
17
<p>Напишем код сохранения в Redis нашей сущности прямо в main, оставив правила приличия тем, кто будет разрабатывать приложения:</p>
18
@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")); } }<p>А теперь начинается самое интересное: давайте посмотрим в Redis, что у нас сохранилось:</p>
18
@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")); } }<p>А теперь начинается самое интересное: давайте посмотрим в Redis, что у нас сохранилось:</p>
19
127.0.0.1:6379> keys * 1) "employee:1b06b207-a453-4250-89ac-9c4f83d437d9" 2) "employee" 3) "employee:9b09223d-b9c1-4fd4-abd8-c0f52ed432b0"<p>Вот и разберёмся, как реализуется @RedisHash("employee") в Redis. Сначала разберёмся, что же лежит по ключу "employee":</p>
19
127.0.0.1:6379> keys * 1) "employee:1b06b207-a453-4250-89ac-9c4f83d437d9" 2) "employee" 3) "employee:9b09223d-b9c1-4fd4-abd8-c0f52ed432b0"<p>Вот и разберёмся, как реализуется @RedisHash("employee") в Redis. Сначала разберёмся, что же лежит по ключу "employee":</p>
20
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"<p>Да, здесь лежат все ключи всех Person-ов. Теперь понятно, как можно реализовать, например, метод<strong>count</strong>-репозитория. А функционал PagingAndSortingRepository теперь не кажется фантастикой. Узнаем теперь, что лежит по ключу конкретного Person:</p>
20
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"<p>Да, здесь лежат все ключи всех Person-ов. Теперь понятно, как можно реализовать, например, метод<strong>count</strong>-репозитория. А функционал PagingAndSortingRepository теперь не кажется фантастикой. Узнаем теперь, что лежит по ключу конкретного Person:</p>
21
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"<p>Как и следовало ожидать - это уже знакомый нам<strong>hash</strong>. Создадим более сложную сущность, и сериализация станет хитрее. Введём другой класс -<strong>Address</strong>. Обратите внимание, этот класс не имеет аннотацию @Id.</p>
21
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"<p>Как и следовало ожидать - это уже знакомый нам<strong>hash</strong>. Создадим более сложную сущность, и сериализация станет хитрее. Введём другой класс -<strong>Address</strong>. Обратите внимание, этот класс не имеет аннотацию @Id.</p>
22
public class Address { private String city; private String street; private String number; // конструктор, геттеры и сеттеры }<p>Добавим ссылку на данный класс:</p>
22
public class Address { private String city; private String street; private String number; // конструктор, геттеры и сеттеры }<p>Добавим ссылку на данный класс:</p>
23
@RedisHash("employee") public class Person { @Id private String id; private String firstName; private String lastName; private Address address; // конструктор, геттеры и сеттеры }<p>Создадим и запишем соответствующий объект:</p>
23
@RedisHash("employee") public class Person { @Id private String id; private String firstName; private String lastName; private Address address; // конструктор, геттеры и сеттеры }<p>Создадим и запишем соответствующий объект:</p>
24
Address omsk = new Address("Omsk", "Lenina", "2"); repository.save(new Person("Cidre", "Sidorov", omsk));<p>А теперь его прочитаем:</p>
24
Address omsk = new Address("Omsk", "Lenina", "2"); repository.save(new Person("Cidre", "Sidorov", omsk));<p>А теперь его прочитаем:</p>
25
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"<p>Подобный вид сериализации внутренних классов называется<strong>Flat Hash Mapping</strong>. Его, кстати, можно настроить вплоть до хранения строки<strong>JSON</strong>. Делается это различными способами.</p>
25
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"<p>Подобный вид сериализации внутренних классов называется<strong>Flat Hash Mapping</strong>. Его, кстати, можно настроить вплоть до хранения строки<strong>JSON</strong>. Делается это различными способами.</p>
26
<p>К сожалению, ссылки хэшей друг-на друга - аналог @OneToOne и других аннотаций связей не поддерживаются автоматически в режиме репозиториев.</p>
26
<p>К сожалению, ссылки хэшей друг-на друга - аналог @OneToOne и других аннотаций связей не поддерживаются автоматически в режиме репозиториев.</p>
27
<p>Ну и напоследок стоит сказать про дополнительные методы, которые можно добавлять в репозиторий:</p>
27
<p>Ну и напоследок стоит сказать про дополнительные методы, которые можно добавлять в репозиторий:</p>
28
public interface PersonRepository extends KeyValueRepository<Person, String> { List<Person> findByFirstName(String firstName); List<Person> findByLastName(String lastName); }<p>Да, традиционно для Spring Data реализации этих методов напишет за вас<strong>Spring Data</strong>. И встаёт логичный вопрос - а как будет реализован поиск, если нет никаких индексов? Да, всё верно - будет реализован полным поиском со сложностью o(n). И, тем не менее, есть возможность организовать с помощью структур Redis подобный индекс:</p>
28
public interface PersonRepository extends KeyValueRepository<Person, String> { List<Person> findByFirstName(String firstName); List<Person> findByLastName(String lastName); }<p>Да, традиционно для Spring Data реализации этих методов напишет за вас<strong>Spring Data</strong>. И встаёт логичный вопрос - а как будет реализован поиск, если нет никаких индексов? Да, всё верно - будет реализован полным поиском со сложностью o(n). И, тем не менее, есть возможность организовать с помощью структур Redis подобный индекс:</p>
29
@RedisHash("employee") public class Person { @Id private String id; @Indexed private String firstName; @Indexed private String lastName; private Address address; // конструктор, геттеры и сеттеры }<p>Посмотрим, что сохранилось в БД:</p>
29
@RedisHash("employee") public class Person { @Id private String id; @Indexed private String firstName; @Indexed private String lastName; private Address address; // конструктор, геттеры и сеттеры }<p>Посмотрим, что сохранилось в БД:</p>
30
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"<p>Ну и теперь понятно, что собой представляют индексы.</p>
30
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"<p>Ну и теперь понятно, что собой представляют индексы.</p>
31
<p>На этом изучение Spring Data Redis-репозиториев закончено, предлагаю рассмотреть ещё одну важную возможность<strong>Spring Data Redis</strong>в следующей части.</p>
31
<p>На этом изучение Spring Data Redis-репозиториев закончено, предлагаю рассмотреть ещё одну важную возможность<strong>Spring Data Redis</strong>в следующей части.</p>
32
<p><em>Есть вопросы? Напишите в комментариях!</em></p>
32
<p><em>Есть вопросы? Напишите в комментариях!</em></p>
33
33