Статьи

Введение в Redis с помощью Spring Boot

1. Обзор

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

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

2. Что такое Redis?

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

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

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

Сервер доступен бесплатно по адресу http://redis.io/download .

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

1
brew install redis

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

4. Maven Зависимости

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

01
02
03
04
05
06
07
08
09
10
11
12
<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>

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

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

5.1 Конфиг

Давайте начнем с определений компонента конфигурации:

01
02
03
04
05
06
07
08
09
10
11
12
@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 для запроса данных.

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

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

1
2
3
4
public interface MessagePublisher {
 
    void publish(final String message);
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
@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 :

1
2
3
4
@Bean
    MessagePublisher redisPublisher() {
        return new MessagePublisherImpl(redisTemplate(), topic());
    }

Прослушиватель сообщений

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

Таким образом, мы создаем класс сервиса для реализации MessageSubscriber :

01
02
03
04
05
06
07
08
09
10
11
@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()));
    }
 
}

Мы добавляем определение компонента в RedisConfig :

1
2
3
4
@Bean
    MessageListenerAdapter messageListener() {
        return new MessageListenerAdapter(new MessageSubscriber());
    }

6. RedisRepository

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

6.1 Модель

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

1
2
3
private String id;
private String name;
//standard getters and setters

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
public interface RedisRepository {
 
    Map<Object, Object> findAllMovies();
 
    void add(Movie movie);
 
    void delete(String id);
 
    Movie findMovie(String id);
     
}

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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.

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

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

7.1 Добавить фильм

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

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

01
02
03
04
05
06
07
08
09
10
11
<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 для сохранения значений при отправке формы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
$(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 и сохраняем его в хранилище:

01
02
03
04
05
06
07
08
09
10
@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);
    }

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

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

01
02
03
04
05
06
07
08
09
10
11
12
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> :

01
02
03
04
05
06
07
08
09
10
@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;
    }

7.3 Удалить фильм

Мы пишем Javascript для выполнения запроса POST   / удалить , обновить таблицу и установить фокус клавиатуры для клавишного ввода:

1
2
3
4
5
6
function deleteKey(key) {
    $.post('/delete', {key: key}, function() {
        refreshTable();
        $('#keyInput').focus();
    });
}

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

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

8. Демо

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

Здесь мы сняли один фильм:

9. Вывод

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

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

Опубликовано на Java Code Geeks с разрешения Майкла Гуда, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Введение в Redis с помощью Spring Boot

Мнения, высказанные участниками Java Code Geeks, являются их собственными.