redis - java persistence
- Java Redis Client는 다음 두 가지
- Jedis
- Lettuce
- SpringBoot 2.0부터 lettuce가 기본 Client
- Lettuce is a fully non-blocking Redis client built with netty providing Reactive, Asynchronous and Synchronous Data Access .
- https://github.com/lettuce-io/lettuce-core/wiki/Connection-Pooling
spring-boot-starter-data-redis-reactive
연동
build.gradle.kts
```kt
implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
```
AutoConfiguration
```java
@ConfigurationProperties(prefix = "spring.redis") // spring.redis 이하 properties들을 불러온다.
public class RedisProperties {
private String host = "localhost";
private int port = 6370;
...
```
- RedisAutoConfiguration.java에서는 다음 두 가지 Bean을 미리 만들어서 제공하고 있음
- ``kt StringRedisTemplate``
- key, value, hashKey, hashValue의 Serializer로 ``kt RedisSerializer.string()`` 사용
- ``kt RedisTemplate<Object, Object>``
- key, value, hashKey, hashValue의 defaultSerializer로 `` JdkSerializationRedisSerializer`` 사용
- 결국 Java Obj로 변환한다는 건데... 이는 여러모로 단점이 있음. ([Effective Java] 12장 직렬화 참고)
- 그래서 Jackson을 Serializer로 사용하는 `` RestTemplate``을 따로 정의해서 사용하는 것이 좋아 보인다.
- ``kt StringRedisTemplate``
Auto Configuration Disable?
- ``kt RedisReactiveAutoConfiguration::class``는 exclude 가능.
- 반면 ``kt RedisAutoConfiguration::class``는 exclude하면 factory까지 생성이 안돼서 exclude 불가.
예제
- https://spring.io/guides/gs/spring-data-reactive-redis/
- 테스트
- https://daddyprogrammer.org/post/4056/reactive-redis/
- 비동기 처리를 하기 때문에 StepVerifier를 사용해야 함. `` reactor-test`` 의존성 추가 필요.
```kt
@Configuration
class RedisConfig { // 방법 1, 3 공통
@Bean
fun reactiveRedisTemplate(factory: ReactiveRedisConnectionFactory, objectMapper: ObjectMapper): ReactiveRedisTemplate<String, Any> {
val stringSerializer = StringRedisSerializer()
val jsonSerializer = Jackson2JsonRedisSerializer(Any::class.java)
jsonSerializer.setObjectMapper(objectMapper)
val context = RedisSerializationContext.newSerializationContext<String, Any>()
.key(stringSerializer)
.value(jsonSerializer)
.hashKey(stringSerializer)
.hashValue(jsonSerializer)
.build()
return ReactiveRedisTemplate(factory, context)
}
// StringTemplate은 안해도 된다. RedisReactiveAutoConfiguration.java 에서 설정하고 있음.
@Bean
fun reactiveStringRedisTemplate(
reactiveRedisConnectionFactory: ReactiveRedisConnectionFactory
): ReactiveStringRedisTemplate {
return ReactiveStringRedisTemplate(reactiveRedisConnectionFactory)
}
/**
* 방법 1. 이런 식으로 data class 마다 Bean을 직접 만든다. 완전 비추.
*/
@Bean
fun coffeeRedisOperations(factory: ReactiveRedisConnectionFactory): ReactiveRedisOperations<String, Coffee> {
val builder = RedisSerializationContext.newSerializationContext<String, Coffee>(StringRedisSerializer())
val context = builder
.value(Jackson2JsonRedisSerializer(Coffee::class.java))
.build()
return ReactiveRedisTemplate(factory, context)
}
}
/**
* 방법 2. 각 서비스 클래스에서 getRedisTemplate을 호출해서 해당 타입의 RedisTemplate을 반환 받는 방법
*/
@Component
class RedisTemplateFactory(
val connectionFactory: ReactiveRedisConnectionFactory, val objectMapper: ObjectMapper
) {
final inline fun <reified V> getRedisTemplate(): ReactiveRedisTemplate<String, V> {
val stringSerializer = StringRedisSerializer()
val jsonSerializer = Jackson2JsonRedisSerializer(V::class.java)
jsonSerializer.setObjectMapper(objectMapper)
val context = RedisSerializationContext.newSerializationContext<String, V>()
.key(stringSerializer)
.value(jsonSerializer)
.hashKey(stringSerializer)
.hashValue(jsonSerializer)
.build()
return ReactiveRedisTemplate(connectionFactory, context)
}
}
/**
* 방법 3. RestTemplate과 비슷하게 사용하기 위해서...? 내부적으로는 Any로 처리하고 set, get해서 내려줄 때는 Casting해서 내려주는 방법.
* 단점은 set, get 뿐만 아니라 다양한 메서드(keys, expire 등)를 모두 wrapping 해주어야 한다는 점.
*/
@Component
class RedisComponent(val redisTemplate: ReactiveRedisTemplate<String, Any>, val objectMapper: ObjectMapper) {
fun <V> set(key: String, value: V): Mono<Boolean> {
return redisTemplate.opsForValue().set(key, value as Any)
}
/**
* 방법 3-1.
* opsForValue().get()으로 Object를 받고 바깥에서 ObjectMapper.convertValue()를 사용해 V로 변환
*/
fun <V> get(key: String, clazz: Class<V>): Mono<V> {
return redisTemplate.opsForValue().get(key)
.map { obj -> objectMapper.convertValue(obj, clazz) }
}
/**
* 방법 3-2.
* 아예 RedisTemplate 자체를 저수준부터 구현. (RestTemplate 처럼 메서드 기반으로.)
*/
}
```
방법2가 제일 괜찮아 보임.
Spring Data Redis API
- low-level API는 `` RedisConnection`` 계열. binary 통신 제공
- `` RedisClusterConnection``
- 이를 고수준으로 추상화한 API는 `` RedisTemplate`` 계열. 객체 수준으로 다룰 수 있음
- `` ReactiveRedisTemplate``
- RedisTemplate은 클래스이고, 보통 주고 받을 때는 `` RedisOperations``라는 인터페이스를 사용한다
- ``kt RedisOperations.opsForValue()`` 요런 식으로 있다고 보면 됨
- (아래) Template = connection + serializer 정도로 생각하면 된다.
```kt
@Bean
fun redisOperations(factory: ReactiveRedisConnectionFactory): ReactiveRedisOperations<String, Coffee> {
val builder = RedisSerializationContext.newSerializationContext<String, Coffee>(StringRedisSerializer())
val context = builder.value(Jackson2JsonRedisSerializer(Coffee::class.java)).build()
return ReactiveRedisTemplate(factory, context)
}
```
Redis Cluster
- Redis 여러 인스턴스를 묶어서 트래픽 분산, High Performance 제공
- Master-Slave 구조로 구성하면 장애 시 복구 가능
- https://daddyprogrammer.org/post/1601/redis-cluster/
- https://lettuce.io/core/release/reference/#redis-cluster.connection-count
- https://lettuce.io/core/release/reference/#clientoptions.request-queue-size-and-cluster
Redis도 DB처럼 Mapper 사용해서 CRUD하기?
class 이름 변경, 패키지 변경 시 매우 주의!! redis 해시에 _class 필드로 해당 클래스 이름(+패키지 경로)가 들어가기 때문!!
마찬가지로 필드명 변경도 주의해야 한다.
- https://www.baeldung.com/spring-data-redis-tutorial
- 단, 기본 클라이언트가 Jedis에서 Lettuce로 변경되었으니 이를 사용하는 것 권장
- https://basketdeveloper.tistory.com/77
- https://docs.spring.io/spring-data/data-redis/docs/2.6.3/reference/html/#redis.repositories
- 역시 제일 좋은건 공식 docs
- @Indexed 같은 애너테이션에 대한 설명도 잘 나와 있다.
```kt
@Repository
interface AsyncWithdrawItemRepository: CrudRepository<AsyncWithdrawItem, String>
// 이런 구조의 장점은, mocking 하기 쉽다는 것이다.
class MockAsyncWithdrawItemRepository: AsyncWithdrawItemRepository {...}
```
@RedisHash & Repository 사용하는 방법 VS 방법2-RedisTemplateFactory 비교
- (1:1) : `` RedisTemplateFactory`` - 메타 데이터나 설정 데이터 같은, 클래스는 존재하지만 개념적으로 해당 객체가 유니크한 경우
- (1:n) : `` @RedisHash & Repository`` - Student 클래스 같이, 여러 인스턴스가 존재할 수 있으며 각각이 고유한 id 별로 식별되어야 하는 경우
- reactive가 필요하거나, json serialize/deserialize 가 필요한 경우 : `` RedisTemplateFactory``
- `` @RedisHash & Repository``는 redis 타입 hash, `` RedisTemplateFactory``는 json이므로 redis 타입 string
'Java Stack > Persistence' 카테고리의 다른 글
Spring JDBC와 JdbcOperations (0) | 2023.08.10 |
---|---|
[MyBatis] 객체 안의 객체 매핑하기 (ResultMap과 DTO) (0) | 2022.03.12 |
[MyBatis] Cache (0) | 2022.03.08 |
[Spring] DB 관련 : Mybatis (3) | 2020.03.04 |