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.
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:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
We are configure Jedis as a Spring Bean creating a class called RedisConfig.java and define the host and port of Redis-Server in the application.properties.
spring.redis.host=localhost
spring.redis.port=6379
RedisConfig.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) that will use to record the information in Redis.
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.
@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).
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.
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
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {
private final ProductCacheRepository productCacheRepository;
public Product save(Product product) {
return this.productCacheRepository.save(product);
}
}
- ProductRestController.java
@RestController
@RequiredArgsConstructor
@RequestMapping("/products")
public class ProductRestController {
public final ProductService productService;
@PostMapping
public Product save(@RequestBody Product product) {
return this.productService.save(product);
}
}
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.
To check the type of each key, we can use the TYPE command.
Now we're going create an endpoint that lists all the products saved in Redis. Let's change our ProductJedisRepository.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 that we use to iterate and get each item and add it to a new list (List).
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
@Override
public List<Product> findAll() {
return this.productCacheRepository.findAll();
}
- ProductRestController.java
@GetMapping
public List<Product> getAll() {
return this.productService.findAll();
}
Now we will list the same via postman :)
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.
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 as a Spring-Data-Redis Client.
<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.
@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.
@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.
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 class was as follows (attention to @Qualifier(“dataRepository”)):
@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.
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/#
Top comments (0)