Redis & Spring-Data-Redis
Published: 2020-02-27 02:50:36

## What will be covered in this post...?

- Redis Data Structure, concepts and commands
- Jedis-Client to communicate with Redis-Server
- Data Structure that Spring-Data-Redis uses to store the data
   
Redis means **REmote DIctionary Server**. Unlike a traditional database like MySQL or Oracle, it's categorized as a non-relational database, and is often referred to by the acronym [NOSQL](https://pt.wikipedia.org/wiki/NoSQL).   
      
A very important feature about Redis is that it stores your data in memory, althout it's possible to persist the data physically. But is the fact that Redis to store the data in memory that makes it extremely fast, both for writing and reading data.
   
Redis store the data in the form of key-value, however, an interesting point about the Redis Data Structure is that the value contained in a registry key, supports differents formats, which can be String, Hashes, Lists, Sets and Ordered Sets.
   
> The main data types in Redis are basically 5, **Strings, Lists, Sets, Hashes and Ordered Sets**. To know more about each type, access the *Redis Documention*. → https://redis.io/topics/data-types   
   
Clients DON'T use the HTTP protocol, they communicate using a protocol called RESP (REdis Serialization Protocol).   
   
## Creating the Java Application   
   
In this post we will use 2 types of Data Structure, **SET** and **HASH**.   
**SET** is similar to the `LIST` type, but doesn't allow equal values, if an existing to added it will be ignored. We will use SET to add a member to the `"products"` key list.   
**HASH** is nothing more than a map that contains fields and values of the type String. This data type is widely used to represent objects defined in our applications, such as a Product that contains a name, price and brand.   
   
In Java we will use [Jedis as Client](https://github.com/xetorthio/jedis):   
   
```xml
<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
</dependency>
```   
      
We are configure **Jedis** as a **Spring Bean** creating a class called [RedisConfig.java](https://github.com/diegolirio/demos-spring-boot/blob/master/redis-spring-data-redis/src/main/java/com/diegolirio/redisspringdataredis/config/RedisConfig.java) and define the host and port of Redis-Server in the **application.properties**.   
   
```properties
spring.redis.host=localhost
spring.redis.port=6379
```   
   
RedisConfig.java   
   
```java
@Configuration
public class RedisConfig {
  @Bean
  public Jedis jedis(@Value("${spring.redis.host}") final String host,
                   @Value("${spring.redis.port}") final int port) {
        return new Jedis(host, port);
  }
}
```   
       
Below we have the entity Product ([Product.java](https://github.com/diegolirio/demos-spring-boot/blob/master/redis-spring-data-redis/src/main/java/com/diegolirio/redisspringdataredis/product/domain/Product.java)) that will use to record the information in Redis.   
    
```java
public class Product {

    private String id;
    private String name;
    private String price;
    private String marca;
}
```   
   
Now we will use Jedis (bean) to save the data in Redis-Server and create the **save(Product)** method.   
   
```java
@Repository
@RequiredArgsConstructor
public class ProductJedisRepository implements ProductCacheRepository {

    private final Jedis jedis;

    private final static String PRODUCTS = "products";
    private static String keyHash = PRODUCTS.concat(":%s");

    @Override
    public Product save(Product product) {
        Map<String, String> mapProduct = (Map<String, String>) 
                           this.convertValue(product, Map.class);
        this.jedis.hmset(String.format(keyHash, product.getId()), 
                         mapProduct);
        this.jedis.sadd(PRODUCTS, product.getId());
        return product;
    }
    public Object convertValue(Object object, Class clazz) {             
       return new ObjectMapper().convertValue(object, clazz);
    }

}
```   
   
In the save(product) method we use 2 Jedis methods, `jedis.hmset(String key, Map<String, String> hash)` and `jedis.sadd(String key, String... members)`.   
   
We first add the item to the **HASH** Data Structure and then add the item to the products list in the **SET** Structure.   
   
The jedis.hmset(key, hash) method represents **HMSET** command of the HASH Data Structure, used to save the item. In the key we used the standard **“products:123456789”,** **prefix is the list** that the item belongs to, **colon** and **item key** (ID).   
![](https://miro.medium.com/max/747/1*0DDMewQgfwm85YJsy7sY5g.png)      
   
The jedis.sadd(key, hash) method represents a SET command, in Redis called **SADD** to add a set represented by a key and its respective items (values). The SADD command adds one or more items to the set (key) informed as parameter.   
![](https://miro.medium.com/max/625/1*8XZ38tXGqpn_mKRduuYHpg.png)   
   
> TIP: To run a local Redis you can follow the instructions on the link → https://gist.github.com/diegolirio/fcd6642b48509a4d687b25aa3027030a#installation   
   
We will create the Service and Controller, execute and verify in Redis how our objects were added.   
      
- ProductServiceImpl.java    

```java
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {

    private final ProductCacheRepository productCacheRepository;

    public Product save(Product product) {
        return this.productCacheRepository.save(product);
    }
}
```   
   
- ProductRestController.java    

```java
@RestController
@RequiredArgsConstructor
@RequestMapping("/products")
public class ProductRestController {

    public final ProductService productService;

    @PostMapping
    public Product save(@RequestBody Product product) {
        return this.productService.save(product);
    }
}
```   
   
Running on postman:   
![](https://miro.medium.com/max/953/1*2vD67XyMYmtRUyEwgPYukA.png)   
     
We will use Redis-CLI to check what was created by our Java code. The first command used is `KEYS *` to list all of our keys.   
![](https://miro.medium.com/max/633/1*XMQsxLPg8KVH4j07hdknJQ.png)   
   
To check the type of each key, we can use the **TYPE** command.    
![](https://miro.medium.com/max/645/1*3r95KnhX9LYMONaJTxC80A.png)   
   
Now we're going create an endpoint that lists all the products saved in Redis. Let's change our ProductJedisRepository.java.
   
```java
@Override
public List<Product> findAll() {
    Set<String> smembers = this.jedis.smembers(PRODUCTS);
    List<Product> list = new ArrayList<>();
    smembers.forEach(member -> {
        Map<String, String> map = this.jedis.hgetAll(String.format(keyHash, member));
        Product product = (Product) this.convertValue(map, Product.class);
        list.add(product);
    });
    return list;
}
```
   
We created a new method called findAll and in this method we used 2 new  jedis’ methods, `smembers(String key)` and `hgetall(String key)`.    
   
The smembers(key) method also represents a SET command, equivalent to the **SMEMBERS** command in Redis, the command returns all elements of a set defined by the informed key. The jedis.smembers method returns a Set<String> that we use to iterate and get each item and add it to a new list (List<Product>).  
    
We also use hgetall(key) that represents **HGETALL** command of the HASH Structure, this command lists all fields and product values. The jedis.hgetall(key) returns a map that represents exactly the fields (key) and values (value), in the code we convert it into a Product object.   
   
Now let's change the service and controller.   
    
- *ProductServiceImpl.java*   

```java
@Override
public List<Product> findAll() {
    return this.productCacheRepository.findAll();
}
```   
   
- *ProductRestController.java*    
```java
@GetMapping
public List<Product> getAll() {
    return this.productService.findAll();
}
```  
    
Now we will list the same via postman :)    
![](https://miro.medium.com/max/961/1*jy-Wbba0_3EDnMuJgX2XSg.png)   
    
After execute we receive the set of products. Bellow we have the command that were executed in Redis (CLI).  
    
> TIP: The commands usually indicate in the first letter the Structure Data Type. Ex: SMEMBERS | HGETALL = HASH.      
   
![](https://miro.medium.com/max/623/1*80-_JxMppjAnp9ANx-GOhQ.png)   
   
## Spring Data Redis   
   
Spring Data Redis project applies the main concepts of Spring, using a key-value style data store. Providing a "template" as a high-level abstraction. You can see similarities to the JDBC support in the Spring Framework.    
    
We will add the Spring-Data-Redis dependency to our project, and also as we’re using Jedis as a Client we will exclude [Lettuce](https://github.com/lettuce-io/lettuce-core) as a Spring-Data-Redis Client.   
   
```xml
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
   <exclusions>
      <exclusion>
         <groupId>io.lettuce</groupId>
         <artifactId>lettuce-core</artifactId>
      </exclusion>
   </exclusions>
</dependency>
```   
   
Lettuce has been the default client for quite a while until in 2014 Lettuce was no longer maintained(Lettuce is used by spring-data-redis-reactivate). Jedis has become the standard client for Java applications (spring-data-redis). Adoption of Spring Data Redis about 2 years ago and Spring Boot only recently (with 2.0 M1).   
    
> TIP: See the github discussions about using Lettuce or Jedis.
→ https://github.com/spring-projects/spring-boot/issues/9536
→https://github.com/spring-projects/spring-boot/issues/745   
   
Let's configure Spring-Data's RedisTemplate changing the RedisConfig.java class.   
   
```java
@Bean
public JedisConnectionFactory redisConnectionFactory(final String host, final int port) {
    return new JedisConnectionFactory(new RedisStandaloneConfiguration(host, port));
}

@Bean
public RedisTemplate<?, ?> redisTemplate(@Value("${spring.redis.host}") final String host,
                                         @Value("${spring.redis.port}") final int port) {    
    RedisTemplate<String, Object> template = new RedisTemplate<>();    
    template.setConnectionFactory(redisConnectionFactory(host,port));
    return template;
}
```   
   
Most developers probably use RedisTemplate. The template is in fact the core class of Redis module due to its rich set of features, it offers a high-level abstraction for Redis interactions. While RedisConnection offers low-level methods that accept and return binary values (byte arrays), the model takes care of serialization and connection management, freeing the dev to deal with these details.   
   
Then let's change our Product class, we will add the Annotation @RedisHash passing the "products" value and also the @Id int the id field.    
   
```java
@RedisHash("products")
public class Product {
    @Id
    private String id;
    private String name;
    private String price;
    private String marca;
}
```   
    
We will now add a new interface, it will extends ProductCacheRepository and CrudRepository.   
   
```java
package com.diegolirio.redisspringdataredis.product.domain;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository("dataRepository")
public interface ProductStringDataRepository extends ProductCacheRepository, CrudRepository<Product, String> {}
```   
   
In [ProductServiceImpl.java](https://github.com/diegolirio/demos-spring-boot/blob/master/redis-spring-data-redis/src/main/java/com/diegolirio/redisspringdataredis/product/application/ProductServiceImpl.java) class was as follows (attention to @Qualifier(“dataRepository”)):   
   
```java
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {

    @Autowired
    @Qualifier("dataRepository")
    private ProductCacheRepository productCacheRepository;

    public Product save(Product product) {
        return this.productCacheRepository.save(product);
    }

    @Override
    public List<Product> findAll() {
        return this.productCacheRepository.findAll();
    }
}
```   
    
We restarted the application and executed the endpoint via postman again, result was the same, when entering Redis-CLI and execute `KEYS *` and `SMEMBERS` (image bellow) the result was also equivalent.
When we execute `HGETALL` command we have a small change, a new field called **“_class”** and then **“com.diegolirio.redisspringdataredis.product.domain.Product”** value.   
     
> TIP: if you want to run Redis commands via CLI without installing it, you can use http://try.redis.io, a web application made in Ruby totaly online.    
    
![](https://miro.medium.com/max/645/1*AdNd7wDOcPHBWcHOi2U_5A.png)   
    
Spring-Data-Redis saves a  “_class” field to identify which class it is, to serialize it via Reflection in the find for this data. @Id identifies the key when using this annotation in the id field.     
      
We realized that the structure created by Spring-Data-Redis was the same that we created before only with Jedis-Client.    
     
> The purpose here was to show how Spring-Data-Redis creates Data Structure in Redis, a little of the data structure and commands, to know what's behind the spring-data-redis. Of course the algorithm can be (and is) quite different, spring-data-redis is much more abstract and works in a more general way.   
     
If you want to go deeper into the subject I left the links that I used as reference in this post.   
     
Thanks, see you later ;-)   
   
**Github:** https://github.com/diegolirio/demos-spring-boot/tree/master/redis-spring-data-redis   
   
Ref.:   
   
https://www.casadocodigo.com.br/products/livro-redis   
   
https://redis.io/topics/protocol   
   
https://redis.io/topics/data-types   
   
https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#