Redis и Spring Data с самого начала. Часть 2 | OTUS

Redis и Spring Data с самого начала. Часть 2

Spring_Deep_13.5_site-5020-b93a2a.png

В предыдущей части мы рассмотрели 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 – ну, ради этого, собственно, и написан стартер. Вводит различные *Operations, а также аннотацию @RedisHash; • Spring Data Key Value – про него мы уже сказали, он вводит абстракцию KeyValueRepository; • Spring Data – вводит собственную аннотацию @Id; • Lettuce – специальный проект Pivotal для непосредственного подключения к Redis. Использует Netty и Project Reactor (RxJava – опциональна, по желанию). • Spring Tx для управления транзакциями. Да, всё так – можно делать транзакции на Redis-е. Но, к сожалению, работа с транзакциями должна быть выключена при работе с репозиториями.

Напишем наш объект, который будет храниться в Redis (будем считать, что весь бойлерплейт написан хитрым lombok, работающем на комментариях):

@RedisHash("employee")
public class Person {
    @Id
    private String id;
    private String firstName;
    private String lastName;

    // конструктор, геттеры и сеттеры 
}

Это код очень похож на JPA Entity, но имеет некоторые отличия. Во-первых, аннотация @Id – это совсем не JPA-аннотация, а собственная аннотация Spring Data, служит для поддержания абстракций Spring Data.

Другая аннотация – @RedisHash, это уже аннотация из Spring Data Redis, синоним аннотации @KeySpace в Spring Data Key Value. C помощью неё можно задать пространство ключей – key space. Можно считать, что это что-то вроде таблицы с sequence в терминах SQL или коллекции в терминах MongoDB. Что будет представлять key space в Redis, мы узнаем совсем скоро.

Ставить эту аннотацию не обязательно – считается, что она стоит по умолчанию с именем класса, т. е. @RedisHash("ru.full.pakcage.name.Person").

Итак, у нас есть объект, теперь нам необходимо написать репозиторий (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 не добавляет ни одного метода), но, к сожалению, сканирование репозиториев в spring-boot-starter-data-redis настроено только на 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"

Вот и разберёмся, как реализуется @RedisHash("employee") в Redis. Сначала разберёмся, что же лежит по ключу "employee":

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-репозитория. А функционал PagingAndSortingRepository теперь не кажется фантастикой. Узнаем теперь, что лежит по ключу конкретного Person:

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. Обратите внимание, этот класс не имеет аннотацию @Id.

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. Делается это различными способами.

К сожалению, ссылки хэшей друг-на друга – аналог @OneToOne и других аннотаций связей не поддерживаются автоматически в режиме репозиториев.

Ну и напоследок стоит сказать про дополнительные методы, которые можно добавлять в репозиторий:

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 в следующей части.

Есть вопросы? Напишите в комментариях!

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто