{getToc} $title={Table of Contents}
|
|
Spring Data Redis Reactive |
In this post, we’ll implement a sample REST API that uses Spring WebFlux with
Spring Data Redis Reactive.
Redis is an
open-source (BSD licensed), in-memory data structure store used as a database,
cache, message broker, and streaming engine. Redis provides data structures
such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps,
hyperloglogs, geospatial indexes, and streams.
Prerequisites
- Spring Boot 2.6.5
- Maven 3.6.+
- Java 11 or later
- Redis 3.2 or later
Getting Started
We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Spring Reactive Web, Spring Data Reactive Redis, Lombok, and Validation.
Configuration
One of the first tasks when using Redis and Spring is to establish a
connection with our Redis server.
We need to create our first reactiveRedisConnectionFactory bean with the
hostname and port of the Redis server.
ReactiveRedisConfiguration.java
@Configuration
public class ReactiveRedisConfiguration {
private final Environment env;
public ReactiveRedisConfiguration(Environment env) {
this.env = env;
}
@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory(Objects.requireNonNull(env.getProperty("spring.redis.host")),
Integer.parseInt(Objects.requireNonNull(env.getProperty("spring.redis.port"))));
}
@Bean
public ReactiveRedisOperations<String, Object> redisOperations(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisSerializationContext.RedisSerializationContextBuilder<String, Object> builder =
RedisSerializationContext.newSerializationContext(new StringRedisSerializer());
RedisSerializationContext<String, Object> context = builder.value(serializer).hashValue(serializer)
.hashKey(serializer).build();
return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory, context);
}
}
The
LettuceConnectionFactory class is the factory that creates
Lettuce-based connections. It create a new LettuceConnection on each call to
getConnection().
The second step in the configuration is to add a second redisOperations bean
method, which takes a ReactiveRedisConnectionFactory and returns a
ReactiveRedisTemplate. This bean uses the Jackson library by configuring a
Jackson2JsonRedisSerializer to perform automatic
serialization/deserialization between the given objects and the underlying
binary data in the Redis store. We can inject this bean wherever we need to
access Redis
CRUD API
To get started, we need a model class. For this post, we have a Book model
class.
@RedisHash
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
@NotNull
private String id;
private String title;
private int page;
private String isbn;
private String description;
private double price;
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate publicationDate;
private String language;
}
-
@RedisHashannotation marks Objects as aggregate roots to be stored in a Redis hash.
@Idto provide primary keys to mapped objects.
We now have the generic component ReactiveRedisComponent which contains
all the common crud methods. This class will be used with different repository
classes.
ReactiveRedisComponent.java
@SuppressWarnings("rawtypes")
@Slf4j
@Component
public class ReactiveRedisComponent {
private final ReactiveRedisOperations<String, Object> redisOperations;
public ReactiveRedisComponent(ReactiveRedisOperations<String, Object> redisOperations) {
this.redisOperations = redisOperations;
}
/**
* Set key and value into a hash key
* @param key key value - must not be null.
* @param hashKey hash key value - must not be null.
* @param val Object value
* @return Mono of object
*/
public Mono<Object> set(String key, String hashKey, Object val) {
return redisOperations.opsForHash().put(key, hashKey, val).map(b -> val);
}
/**
* @param key key value - must not be null.
* @return Flux of Object
*/
public Flux<Object> get(@NotNull String key){
return redisOperations.opsForHash().values(key);
}
/**
* Get value for given hashKey from hash at key.
* @param key key value - must not be null.
* @param hashKey hash key value - must not be null.
* @return Object
*/
public Mono<Object> get(String key, Object hashKey) {
return redisOperations.opsForHash().get(key, hashKey);
}
/**
* Delete a key that contained in a hash key.
* @param key key value - must not be null.
* @param hashKey hash key value - must not be null.
* @return 1 Success or 0 Error
*/
public Mono<Long> remove(String key, Object hashKey) {
return redisOperations.opsForHash().remove(key, hashKey);
}
}
RedisBookRepository.java
@Repository
@RequiredArgsConstructor
public class RedisBookRepository implements BookRepository {
private final ReactiveRedisComponent reactiveRedisComponent;
@Override
public Mono<Book> save(Book book) {
return reactiveRedisComponent.set(BOOK_KEY, book.getId(), book).map(b -> book);
}
@Override
public Mono<Book> get(String key) {
return reactiveRedisComponent.get(BOOK_KEY, key).flatMap(d -> Mono.just(ObjectMapperUtils.objectMapper(d, Book.class)));
}
@Override
public Flux<Book> getAll(){
return reactiveRedisComponent.get(BOOK_KEY).map(b -> ObjectMapperUtils.objectMapper(b, Book.class))
.collectList().flatMapMany(Flux::fromIterable);
}
@Override
public Mono<Long> delete(String id) {
return reactiveRedisComponent.remove(BOOK_KEY,id);
}
}
BookServiceImpl.java
@RequiredArgsConstructor
@Service
public class BookServiceImpl implements BookService {
private final RedisBookRepository bookRepository;
@Override
public Mono<Book> create(Book book) {
return bookRepository.save(book);
}
@Override
public Flux<Book> getAll(){
return bookRepository.getAll();
}
@Override
public Mono<Book> getOne(String id){
return bookRepository.get(id);
}
@Override
public Mono<Long> deleteById(String id) {
return bookRepository.delete(id);
}
}
BookController.java
@RestController
@RequestMapping("/v1")
@RequiredArgsConstructor
public class BookController {
private final BookServiceImpl bookService;
@PostMapping("/book")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Book> addBook(@RequestBody @Valid Book book) {
return bookService.create(book);
}
@GetMapping("/book")
public Flux<Book> getAllBooks() {
return bookService.getAll();
}
@GetMapping("/book/{id}")
public Mono<Book> getBook(@PathVariable String id) {
return bookService.getOne(id);
}
@DeleteMapping("/book/{id}")
public Mono<Long> deleteBook(@PathVariable String id) {
return bookService.deleteById(id);
}
}
