Введение в Redis с использованием Spring Boot

в 12:59, , рубрики: crud, database, java, redis, spring boot, spring data, tutorial, Блог компании OTUS. Онлайн-образование, Программирование

Перевод статьи подготовлен специально для студентов курса «Разработчик на Spring Framework».


В этой статье мы рассмотрим основы использования Redis через Spring Boot с помощью библиотеки Spring Data Redis.

Введение в Redis с использованием Spring Boot - 1

Мы создадим приложение, которое демонстрирует, как выполнять CRUD-операции через веб-интерфейс. Исходный код этого проекта доступен на GitHub.

Что такое Redis?

Redis — это хранилище данных с открытым исходным кодом, для структур данных «ключ-значение», которое можно использовать в качестве базы данных, кэша и брокера сообщений. С точки зрения реализации, хранилища «ключ-значение» являются одними из самых больших и старых представителей в мире NoSQL. Redis поддерживает такие структуры данных, как строки, хэши, списки, множества и отсортированные множества с запросами диапазонов.

Фреймворк Spring Data Redis дает возможность простого написания Spring-приложений, которые используют хранилище Redis, предоставляя удобную абстракцию хранилища данных.

Настройка сервера Redis

Сервер доступен бесплатно здесь.
Если вы используете Mac, вы можете установить его с помощью homebrew:

brew install redis

Затем запустите сервер:

mikes-MacBook-Air:~ mike$ redis-server
10699:C 23 Nov 08:35:58.306 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
10699:C 23 Nov 08:35:58.307 # Redis version=4.0.2, bits=64, commit=00000000, modified=0, pid=10699, just started
10699:C 23 Nov 08:35:58.307 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
10699:M 23 Nov 08:35:58.309 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 4.0.2 (00000000/0) 64 bit
  .-`` .-```.  ```/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 10699
  `-._    `-._  `-./  _.-'    _.-'                                  
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                      
          `-._        _.-'                                           
              `-.__.-'                                               
10699:M 23 Nov 08:35:58.312 # Server initialized
10699:M 23 Nov 08:35:58.312 * Ready to accept connections

Maven зависимости

Давайте объявим необходимые зависимости в pom.xml для приложения, с которым будем работать:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Конфигурация Redis

Нам нужно связать наше приложение с сервером Redis. Чтобы установить соединение, мы используем Jedis, клиентскую реализацию Redis.

Конфигурация

Начнем с определений конфигурационных бинов:

@Bean
JedisConnectionFactory jedisConnectionFactory() {
    return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
    final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
    template.setConnectionFactory(jedisConnectionFactory());
    template.setValueSerializer(new GenericToStringSerializer<Object>(Object.class));
    return template;
}

JedisConnectionFactory представлен как bean-компонент, так что мы можем создать RedisTemplate для запроса данных.

Издатель сообщений

Следуя принципам SOLID, мы создаем интерфейс MessagePublisher:

public interface MessagePublisher {
    void publish(final String message);
}

Мы реализуем интерфейс MessagePublisher с использованием высокоуровневого RedisTemplate для публикации сообщения, так как RedisTemplate позволяет передавать произвольные объекты в виде сообщений:

@Service
public class MessagePublisherImpl implements MessagePublisher {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ChannelTopic topic;
    public MessagePublisherImpl() {
    }
    public MessagePublisherImpl(final RedisTemplate<String, Object> redisTemplate, final ChannelTopic topic) {
        this.redisTemplate = redisTemplate;
        this.topic = topic;
    }
    public void publish(final String message) {
        redisTemplate.convertAndSend(topic.getTopic(), message);
    }
}

Мы также определяем это как bean-компонент в RedisConfig:

@Bean
MessagePublisher redisPublisher() {
    return new MessagePublisherImpl(redisTemplate(), topic());
}

Получатель сообщений

Чтобы подписаться на сообщения, необходимо реализовать интерфейс MessageListener: каждый раз, когда приходит новое сообщение, вызывается пользовательский код, находящийся в методе onMessage. Этот интерфейс предоставляет доступ к сообщению, каналу, через который оно было получено, и позволяет использовать любой шаблон, применяемый для подписки на канал.

@Service
public class MessageSubscriber implements MessageListener {
    public static List<String> messageList = new ArrayList<String>();
    public void onMessage(final Message message, final byte[] pattern) {
        messageList.add(message.toString());
        System.out.println("Message received: " + new String(message.getBody()));
    }
}

Также этот класс необходимо зарегистрировать как bean-компонент в RedisConfig:

@Bean
MessageListenerAdapter messageListener() {
    return new MessageListenerAdapter(new MessageSubscriber());
}

RedisRepository

Теперь, когда мы настроили приложение для взаимодействия с сервером Redis, мы подготовим приложение для получения тестовых данных.

Модель

Для этого примера мы определяем модель Movie с двумя полями:

private String id;
private String name;
//standard getters and setters

Интерфейс репозитория

В отличие от других проектов Spring Data, Spring Data Redis предоставляет все необходимое для работы поверх других интерфейсов Spring Data. Это может выглядеть странно для людей, имеющих опыт работы с другими проектами Spring Data.

Часто нет необходимости писать реализацию интерфейса репозитория с проектами Spring Data. Мы просто взаимодействуем с интерфейсом. Spring Data JPA предоставляет многочисленные интерфейсы репозитория, которые могут быть расширены для получения таких функций, как CRUD операции, производные запросы и разбиение на страницы.

Так что, к сожалению, нам нужно написать собственный интерфейс, а затем определить методы:

public interface RedisRepository {
    Map<Object, Object> findAllMovies();
    void add(Movie movie);
    void delete(String id);
    Movie findMovie(String id);
}

Реализация репозитория

Класс использует redisTemplate, определенный в классе конфигурации RedisConfig.

Мы используем HashOperations, который предлагает Spring Data Redis:

@Repository
public class RedisRepositoryImpl implements RedisRepository {
    private static final String KEY = "Movie";
    private RedisTemplate<String, Object> redisTemplate;
    private HashOperations hashOperations;
    @Autowired
    public RedisRepositoryImpl(RedisTemplate<String, Object> redisTemplate){
        this.redisTemplate = redisTemplate;
    }
    @PostConstruct
    private void init(){
        hashOperations = redisTemplate.opsForHash();
    }
    public void add(final Movie movie) {
        hashOperations.put(KEY, movie.getId(), movie.getName());
    }
    public void delete(final String id) {
        hashOperations.delete(KEY, id);
    }
    public Movie findMovie(final String id){
        return (Movie) hashOperations.get(KEY, id);
    }
    public Map<Object, Object> findAllMovies(){
        return hashOperations.entries(KEY);
    }
}

Давайте обратим внимание на метод init(). В этом методе мы используем функцию с именем opsForHash(), она возвращает операции выполняемые с хеш-значениями, привязанными к данному ключу. Затем мы используем hashOps, который был определен в init(), для всех наших CRUD операций.

Веб-интерфейс

В этом разделе мы рассмотрим добавление возможностей CRUD операций Redis в веб-интерфейс.

Добавление фильма

Мы хотим иметь возможность добавить фильм через веб-страницу. Ключ — это идентификатор фильма, а значение — фактический объект. Однако позже мы вернемся к этому, поэтому в качестве значения отображается только название фильма.

Давайте добавим форму в HTML-документ и назначим соответствующие имена и идентификаторы:

<form id="addForm">
<div class="form-group">
                    <label for="keyInput">Movie ID (key)</label>
                    <input name="keyInput" id="keyInput" class="form-control"/>
                </div>
<div class="form-group">
                    <label for="valueInput">Movie Name (field of Movie object value)</label>
                    <input name="valueInput" id="valueInput" class="form-control"/>
                </div>
                <button class="btn btn-default" id="addButton">Add</button>
 </form>

Теперь мы используем JavaScript для сохранения значений при отправке формы:

$(document).ready(function() {
    var keyInput = $('#keyInput'),
        valueInput = $('#valueInput');
    refreshTable();
    $('#addForm').on('submit', function(event) {
        var data = {
            key: keyInput.val(),
            value: valueInput.val()
        };
        $.post('/add', data, function() {
            refreshTable();
            keyInput.val('');
            valueInput.val('');
            keyInput.focus();
        });
        event.preventDefault();
    });
    keyInput.focus();
});

Задаем параметры @RequestMapping для POST запроса, запрашиваем ключ и значение, создаем объект Movie и сохраняем его в хранилище:

@RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<String> add(
    @RequestParam String key,
    @RequestParam String value) {
    Movie movie = new Movie(key, value);
    redisRepository.add(movie);
    return new ResponseEntity<>(HttpStatus.OK);
}

Просмотр контента

Как только объект Movie добавлен, мы обновляем таблицу для отображения новых значений. В блоке JavaScript кода мы вызывали функцию refreshTable(). Она выполняет GET запрос для получения текущих данных в хранилище:

function refreshTable() {
    $.get('/values', function(data) {
        var attr,
            mainTable = $('#mainTable tbody');
        mainTable.empty();
        for (attr in data) {
            if (data.hasOwnProperty(attr)) {
                mainTable.append(row(attr, data[attr]));
            }
        }
    });
}

GET запрос обрабатывается методом findAll(), который извлекает все объекты Movie, хранящиеся в хранилище, а затем преобразует тип данных из Map <Object, Object> в Map <String, String>:

@RequestMapping("/values")
public @ResponseBody Map<String, String> findAll() {
    Map<Object, Object> aa = redisRepository.findAllMovies();
    Map<String, String> map = new HashMap<String, String>();
    for(Map.Entry<Object, Object> entry : aa.entrySet()){
        String key = (String) entry.getKey();
        map.put(key, aa.get(key).toString());
    }
    return map;
}

Удаление фильма

Напишем скрипт для выполнения POST запроса по пути /delete, обновления таблицы и переключения фокуса клавиатуры для удобного ввода:

function deleteKey(key) {
    $.post('/delete', {key: key}, function() {
        refreshTable();
        $('#keyInput').focus();
    });
}

Мы запрашиваем ключ и удаляем объект в redisRepository на основе этого ключа:

@RequestMapping(value = "/delete", method = RequestMethod.POST)
public ResponseEntity<String> delete(@RequestParam String key) {
    redisRepository.delete(key);
    return new ResponseEntity<>(HttpStatus.OK);
}

Демо

Здесь мы добавили два фильма:

Введение в Redis с использованием Spring Boot - 2

И один фильм удалили:

Введение в Redis с использованием Spring Boot - 3

Заключение

В этом руководстве мы рассмотрели Spring Data Redis и один из способов подключения его к веб-приложению для выполнения CRUD-операций.

Исходный код для примера приложения находится на GitHub.

На этом все. Традиционно ждем ваши комментарии.

Автор: MaxRokatansky

Источник


* - обязательные к заполнению поля