Configuration
Dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> |
Specify the redis instance host/port
spring.redis.host=foohost spring.redis.port=6379 |
Enable Redis and set the configuration
import java.util.Collections; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.convert.KeyspaceConfiguration; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.data.redis.serializer.StringRedisSerializer; @EnableRedisRepositories @Configuration public class RedisConfiguration { // Optional : allow to define the key prefix for classes annotated with @RedisHash (instead of the simple class name as default) public static class MyKeyspaceConfiguration extends KeyspaceConfiguration { @Override protected Iterable<KeyspaceSettings> initialConfiguration() { return Collections.singleton(new KeyspaceSettings(Person.class, "people")); } } @Bean RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory rcf) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(rcf); template.setDefaultSerializer(new StringRedisSerializer()); return template; } } |
Multiple API to interact with Redis
RedisTemplate
RedisTemplate and its corresponding package, org.springframework.data.redis.core is the central class of the Redis module, due to its rich feature set. The template offers a high-level abstraction for Redis interactions.
While RedisConnection offers low-level methods that accept and return binary values (byte arrays), the template takes care of serialization and connection management, freeing the user from dealing with such details.
Operational views :
Interface | Description |
---|---|
Key Type Operations | |
| Redis geospatial operations, such as |
| Redis hash operations |
| Redis HyperLogLog operations, such as |
| Redis list operations |
| Redis set operations |
| Redis string (or value) operations |
| Redis zset (or sorted set) operations |
Key Bound Operations | |
| Redis key bound geospatial operations |
| Redis hash key bound operations |
| Redis key bound operations |
| Redis list key bound operations |
| Redis set key bound operations |
| Redis string (or value) key bound operations |
| Redis zset (or sorted set) key bound operations |
Hash mapping
The idea is to map an object of a custom class into a Redis hash.
Several ways :
– Direct mapping, by using HashOperations and a serializer
– Using Redis Repositories
– Using HashMapper and HashOperations
Hash mapping with Redis Repository
Constructor rules for model
If there’s a no-argument constructor, it will be used. Other constructors will be ignored.
If there’s a single constructor taking arguments, it will be used.
If there are multiple constructors taking arguments, the one to be used by Spring Data will have to be annotated with @PersistenceConstructor.
Person Model
package davidxxx; import org.springframework.data.redis.core.RedisHash; // value attr is optional. It allows to specific the prefix key (alternative to the config way seen above) // timeToLive attr is optional. Expressed in seconds. @RedisHash(value = "people", timeToLive = 86400) // 1 days public class Person { @org.springframework.data.annotation.Id private Long id; private String firstname; private String lastname; public Person(Long id, String firstname, String lastname) { this.id = id; this.firstname = firstname; this.lastname = lastname; } public Long getId() { return id; } public String getFirstname() { return firstname; } public String getLastname() { return lastname; } @Override public String toString() { return "Person{" + "id=" + id + ", firstname='" + firstname + '\'' + ", lastname='" + lastname + '\'' + '}'; } } |
Person Repository
import Person; import org.springframework.data.repository.CrudRepository; public interface PersonRepository extends CrudRepository<Person, Long> { } |
Controller
package davidxxx; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.spriangframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.net.URI; import java.sql.SQLException; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @RequestMapping("person") @RestController public class PersonController { private PersonRepository personRepository; public PersonController(PersonRepository personRepository) { this.personRepository = personRepository; } @PostMapping public ResponseEntity<String> addPerson(@RequestBody Person person) throws SQLException { Person saved = personRepository.save(person); return ResponseEntity.created(URI.create("person/" + saved.getId())).body(""); } @GetMapping(value = "/{id}", consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResponseEntity<Person> getPerson(@PathVariable("id") Long id) { return personRepository.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build()); } @GetMapping(consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public List<Person> getAllPerson() { return StreamSupport .stream(personRepository.findAll().spliterator(), false) .collect(Collectors.toList()); } @DeleteMapping(consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) public ResponseEntity<Void> deleteAllPerson() { personRepository.deleteAll(); return ResponseEntity.ok().build(); } } |
Test with curls
# add 2 person curl -v -X POST -H "Content-Type: application/json" --data '{"firstname":"david","lastname":"xxx"}' "localhost:8090/person" curl -v -X POST -H "Content-Type: application/json" --data '{"firstname":"Franck","lastname":"Castle"}' "localhost:8090/person" # find each person since the returned id in the URI curl -v -X GET -H "Content-Type: text/plain" "localhost:8090/person/8667656159371026199" -> {"id":8667656159371026199,"firstname":"david","lastname":"xxx"} curl -v -X GET -H "Content-Type: text/plain" "localhost:8090/person/-5700280251891896039" -> {"id":-5700280251891896039,"firstname":"Franck","lastname":"Castle"} # find all person curl -v -X GET -H "Content-Type: text/plain" "localhost:8090/person" -> [{"id":8667656159371026199,"firstname":"david","lastname":"xxx"},{"id":-5700280251891896039,"firstname":"Franck","lastname":"Castle"}] # delete person curl -v -X DELETE -H "Content-Type: text/plain" "localhost:8090/person" |
Redis Repository constraints
Important document :
https://github.com/spring-projects/spring-data-redis/blob/master/src/main/asciidoc/reference/redis-repositories.adoc
Constraint on collection fields in a RedisHash class
Indeed, we cannot declare any Collection fields in a RedisHash class.
Due to the flat representation structure, Map keys need to be simple types, such as String or Number.
And map field cannot have as values another collection for the same reason.
While we could still mitigate the two issues.
How to allow collection values in a map ?
By default, we may encounter a weird behavior.
For example, with a Map<String, List<String>> map
field, the serialization in redis may store some weird value in the list instead of the real content.
To overcome that, we could define a reader and writer converter for the object to store.