Spring Data Redis

Reactive Redis는 위 문제점을 해결할 수 있는 비동기 논블로킹을 지원하는 레디스 클라이언트 혹은 라이브러리이다.

Untitled

실습

1. build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
    **implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'**
    implementation "io.asyncer:r2dbc-mysql:1.0.2" // spi 1.0.0 (spring boot3)
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}

2. connection 정보 추가

# lazy하게 연결을 해서 참고만 할 뿐 (틀리게 적어도 실행은 된다)
spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3306/sns
    username: root
    password: 1234
  **data:
    redis:
      host: 127.0.0.1
      port: 6379**

lazy하게 연결을 해서 참고만 할 뿐 (틀리게 적어도 실행은 된다)

@Slf4j
@RequiredArgsConstructor
@Configuration
public class RedisConfig implements ApplicationListener<ApplicationReadyEvent> {

    private final ReactiveRedisTemplate<String, String> redisTemplate;

		// 서버 실행 시 연결 검증을 위함
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        redisTemplate.opsForValue().get("1")
                .doOnSuccess(s -> log.info("Initialize to redis connection"))
                .doOnError(e -> log.error("Failed to initialize redis connection: {}", e.getMessage()))
                .subscribe();
    }

    @Bean
    public ReactiveRedisTemplate<String, User> reactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory) {
        ObjectMapper objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) // 모르는 값 무시
                .registerModule(new JavaTimeModule())
                .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

				//  Redis에 데이터를 저장하거나 읽어올 때 사용할 직렬화 컨텍스트 설정
        Jackson2JsonRedisSerializer<User> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, User.class);

        RedisSerializationContext<String, User> serializationContext = RedisSerializationContext
                .<String, User> newSerializationContext()
                .key(RedisSerializer.string())
                .value(jsonRedisSerializer)
                .hashKey(RedisSerializer.string())
                .hashValue(jsonRedisSerializer)
                .build();

        return new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
    }
}

3. service 일부 코드

public Mono<User> findById(Long id) {
    /*
     1. redis 조회
     2. 값이 있으면 응답
     3. 없으면 DB에 쿼리 후 결과를 redis에 저장
    */
    return reactiveRedisTemplate.opsForValue()
        .get(getUserCacheKey(id))
        .switchIfEmpty(
              userR2dbcRepository.findById(id)
                  .flatMap(u -> // redis에 저장
                      reactiveRedisTemplate.opsForValue()
                              .set(getUserCacheKey(id), u, Duration.ofSeconds(30))
                              .then(Mono.just(u))
                  )
        );
}

public Mono<Void> deleteById(Long id) {
    return userR2dbcRepository.deleteById(id)
            .then(reactiveRedisTemplate.unlink(getUserCacheKey(id)))
            .then(Mono.empty());
}

public Mono<User> update(Long id, String name, String email) {
	return userR2dbcRepository.findById(id)
                .flatMap(u -> {
                    u.setName(name);
                    u.setEmail(email);
                    return userR2dbcRepository.save(u);
                })
                .flatMap(u -> reactiveRedisTemplate.unlink(getUserCacheKey(id)).then(Mono.just(u)));
}