- SpringBoot 와 Redis를 이용한 디저트가게 예제
- Redis를 사용하기위해서 디저트가게 예제를 만들어보자
- A dessert shop example using SpringBoot and Redis
- Let's create a dessert shop example to learn how to use Redis
목차
Table of Contents
스택
- SpringBoot
- Lombok
- Spring Reactive Web
- Spring Data Reactive Redis
- Validation
- Gradle
- Java 11 +
- Redis
Tech Stack
- SpringBoot
- Lombok
- Spring Reactive Web
- Spring Data Reactive Redis
- Validation
- Gradle
- Java 11 +
- Redis
레디스서버 띄우기
- 레디스 서버를 실행시켜야 함
- 로컬에서 임베드로 생성하거나, 로컬에 Docker로 생성하거나, 클라우드에 설치 된 redis 사용
- docker-compose를 사용해서 설치 함
- docker-compose.redis.yml
version: "3"
services:
redis-docker:
image: redis:latest
command: redis-server --requirepass qwerqwer123 --port 6379
container_name: "docker-redis"
labels:
- "name=redis"
- "mode=standalone"
volumes:
- /Users/wool/Database-docker/data/redis:/data
ports:
- 6379:6379 - redis에 비밀번호를 적용하지 않는다면
--requirepass옵션을 빼고 작성 - redis서버 실행
$ docker-compose -f docker-compose.redis.yml up -d
- docker-compose.redis.yml
Starting the Redis Server
- You need to run a Redis server
- You can create an embedded instance locally, create one with Docker locally, or use a Redis instance installed in the cloud
- We'll install it using docker-compose
- docker-compose.redis.yml
version: "3"
services:
redis-docker:
image: redis:latest
command: redis-server --requirepass qwerqwer123 --port 6379
container_name: "docker-redis"
labels:
- "name=redis"
- "mode=standalone"
volumes:
- /Users/wool/Database-docker/data/redis:/data
ports:
- 6379:6379 - If you don't want to set a password for Redis, remove the
--requirepassoption - Run the Redis server
$ docker-compose -f docker-compose.redis.yml up -d
- docker-compose.redis.yml
Configuration 작성하기
- ReactiveRedisConfiguration.java
- Redis와 Spring을 연결하기 위해 설정 파일을 작성
reactRedisConnectionFactory빈을 생성하여 레디스 연결 팩토리를 생성혹은@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory(
Objects.requireNonNull(env.getProperty("spring.redis.host")),
Integer.parseInt(Objects.requireNonNull((env.getProperty("spring.redis.port"))))
);
}@Value어노테이션을 사용해서 아래와 같이 데이터를 깔끔하게 정리 할 수 있음@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}- Redis의 데이터를 주고받아야 하기 때문에 JSON형식을 지원하는 Jackson 라이브러리를 사용한 Operation Bean을 세팅한다.
@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);
}
Writing the Configuration
- ReactiveRedisConfiguration.java
- Write a configuration file to connect Redis and Spring
- Create the
reactRedisConnectionFactorybean to establish the Redis connection factoryAlternatively, you can use the@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory(
Objects.requireNonNull(env.getProperty("spring.redis.host")),
Integer.parseInt(Objects.requireNonNull((env.getProperty("spring.redis.port"))))
);
}@Valueannotation to organize the data more cleanly as shown below@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
} - Since we need to send and receive data from Redis, we set up an Operation Bean using the Jackson library which supports JSON format.
@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);
}
CRUD API 작성하기
- CRUD를 작성하기 위해 우선 모델 클래스가 필요
Domain 모델 작성
- 도메인 객체 생성하기
- pk, 제품명, 카테고리, 설명, 가격, 출시일 을 가진 Dessert 도메인객체 생성
Writing the CRUD API
- To write CRUD operations, we first need a model class
Writing the Domain Model
- Creating the domain object
- Create the Dessert domain object with pk, product name, category, description, price, and release date
Database에 접근하는 Repository 작성
- Redis 데이터베이스와 연결 할 Repository를 작성한다
- Interface를 사용해서 CRUD 기능을 명시하고, 구현체를 사용해 각 내부 로직을 구현
- JSON 파싱을 위해서 ObjectMapperUtils 클래스 생성
- Redis와 연결을 위해서 ReactiveRedisComponent 클래스 생성
- DessertRepository
public interface DessertRepository {
Mono<Dessert> save(Dessert dessert);
Mono<Dessert> get(String key);
Flux<Dessert> getAll();
Mono<Long> delete(String id);
} - RedisDessertRepository
public class RedisDessertRepository implements DessertRepository {
private ReactiveRedisOperations<String, Object> redisOperations;
public RedisDessertRepository(ReactiveRedisOperations<String, Object> redisOperations) {
this.redisOperations = redisOperations;
}
@Override
public Mono<Dessert> save(Dessert dessert) {
return redisOperations.opsForValue().set(dessert.getId(), dessert);
}
@Override
public Mono<Dessert> get(String key) {
return redisOperations.opsForValue().get(key);
}
@Override
public Flux<Dessert> getAll() {
return redisOperations.keys("*")
.flatMap(redisOperations::opsForValue::get)
.cast(Dessert.class);
}
@Override
public Mono<Long> delete(String id) {
return redisOperations.delete(id);
}
}
Writing the Repository for Database Access
- Write a Repository to connect to the Redis database
- Use an Interface to define the CRUD functions and implement the internal logic using an implementation class
- Create the ObjectMapperUtils class for JSON parsing
- Create the ReactiveRedisComponent class for connecting to Redis
- DessertRepository
public interface DessertRepository {
Mono<Dessert> save(Dessert dessert);
Mono<Dessert> get(String key);
Flux<Dessert> getAll();
Mono<Long> delete(String id);
} - RedisDessertRepository
public class RedisDessertRepository implements DessertRepository {
private ReactiveRedisOperations<String, Object> redisOperations;
public RedisDessertRepository(ReactiveRedisOperations<String, Object> redisOperations) {
this.redisOperations = redisOperations;
}
@Override
public Mono<Dessert> save(Dessert dessert) {
return redisOperations.opsForValue().set(dessert.getId(), dessert);
}
@Override
public Mono<Dessert> get(String key) {
return redisOperations.opsForValue().get(key);
}
@Override
public Flux<Dessert> getAll() {
return redisOperations.keys("*")
.flatMap(redisOperations::opsForValue::get)
.cast(Dessert.class);
}
@Override
public Mono<Long> delete(String id) {
return redisOperations.delete(id);
}
}
Service 작성
- Interface를 먼저 명시 해 준 후에 구현체로 구현
- DessertService
- DessertService
public interface DessertService {
Mono<Dessert> create(Dessert dessert);
Flux<Dessert> getAll();
Mono<Dessert> getOne(String id);
Mono<Long> deleteById(String id);
} - DessertServiceImpl
@RequiredArgsConstructor
@Service
public class DessertServiceImpl implements DessertService {
private final RedisDessertRepository redisDessertRepository;
@Override
public Mono<Dessert> create(Dessert dessert) {
return redisDessertRepository.save(dessert);
}
@Override
public Flux<Dessert> getAll() {
return redisDessertRepository.getAll();
}
@Override
public Mono<Dessert> getOne(String id) {
return redisDessertRepository.get(id);
}
@Override
public Mono<Long> deleteById(String id) {
return redisDessertRepository.delete(id);
}
}
- DessertService
Writing the Service
- First define the Interface, then implement it with a concrete class
- DessertService
- DessertService
public interface DessertService {
Mono<Dessert> create(Dessert dessert);
Flux<Dessert> getAll();
Mono<Dessert> getOne(String id);
Mono<Long> deleteById(String id);
} - DessertServiceImpl
@RequiredArgsConstructor
@Service
public class DessertServiceImpl implements DessertService {
private final RedisDessertRepository redisDessertRepository;
@Override
public Mono<Dessert> create(Dessert dessert) {
return redisDessertRepository.save(dessert);
}
@Override
public Flux<Dessert> getAll() {
return redisDessertRepository.getAll();
}
@Override
public Mono<Dessert> getOne(String id) {
return redisDessertRepository.get(id);
}
@Override
public Mono<Long> deleteById(String id) {
return redisDessertRepository.delete(id);
}
}
- DessertService
Contoller 생성
- DessertController를 생성하여 API Call을 Response 할 수 있도록 작성
@RestController
@RequestMapping("/v1")
@RequiredArgsConstructor
public class DessertController {
private final DessertServiceImpl dessertService;
@PostMapping("/dessert")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Dessert> addDessert(@RequestBody @Valid Dessert dessert) {
return dessertService.create(dessert);
}
@GetMapping("/dessert")
public Flux<Dessert> getAllDessert() {
return dessertService.getAll();
}
@GetMapping("/dessert/{id}")
public Mono<Dessert> getDessert(@PathVariable String id) {
return dessertService.getOne(id);
}
@DeleteMapping("/dessert/{id}")
public Mono<Long> deleteDessert(@PathVariable String id) {
return dessertService.deleteById(id);
}
}
Creating the Controller
- Create the DessertController to handle API call responses
@RestController
@RequestMapping("/v1")
@RequiredArgsConstructor
public class DessertController {
private final DessertServiceImpl dessertService;
@PostMapping("/dessert")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Dessert> addDessert(@RequestBody @Valid Dessert dessert) {
return dessertService.create(dessert);
}
@GetMapping("/dessert")
public Flux<Dessert> getAllDessert() {
return dessertService.getAll();
}
@GetMapping("/dessert/{id}")
public Mono<Dessert> getDessert(@PathVariable String id) {
return dessertService.getOne(id);
}
@DeleteMapping("/dessert/{id}")
public Mono<Long> deleteDessert(@PathVariable String id) {
return dessertService.deleteById(id);
}
}