사전 조건
- AWS Elasticache 설정
- Redis Cluster을 설정합니다. (기본적으로 3개의 node 설정)
- Redis-cli 접속 가이드
- 간단한 명령어로 테스트 가능(get,set)
- redis-cli -h 호스트 -p 포트
- Java의 Redis 클라이언트
- Redis 클라이언트로는 크게 Jedis, Lettuce가 존재합니다.
- Lettuce : Netty 기반의 클라이언트로, 비동기로 요청 처리하여 고성능을 자랑함.
- Jedis : 동기적으로 요청을 처리, 이전부터 자바의 표준 Redis Client)
- Jedis보다는 Lettuce를 사용하는 것을 권장(https://jojoldu.tistory.com/418)
- 스프링부트 2.0부터는 Lettuce가 기본 클라이언트가 되어서 Jedis를 사용하려면 제거해야만 함
- Lettuce 공식문서
Lettuce Config
@Bean
fun redisConnectionFactory(): RedisConnectionFactory =
LettuceConnectionFactory(RedisStandaloneConfiguration("server", 6379))
@Bean
fun redisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, String> {
val template = RedisTemplate<String, String>()
template.setConnectionFactory(redisConnectionFactory)
return template
}
- server 위치에 레디스 서버 주소를 기입하여 RedisConnectionFactory를 만듭니다.
- 추후 RedisTemplate에서 ConnectionFactory를 지정할 때 사용해 주어야 합니다.
- AWS Elasticache를 사용하는 경우 “server” 위치에 reader가 아닌 기본 엔드포인트를 기재합니다.
- read 목적인 경우 reader로 사용해도 되기는 하지만 write도 같이 사용하기 위해서는 기본 엔드포인트를 사용합니다
Simple Lettuce RW
@Repository
class StudentRedisRepository(
private val redisTemplate: RedisTemplate<String, String>,
private val objectMapper: ObjectMapper,
) {
fun save(student: Student) {
println("deserialize: ${objectMapper.writeValueAsString(student)}")
redisTemplate.opsForValue().set(student.id, objectMapper.writeValueAsString(student))
}
fun getStudent(id: String): Student {
val studentJson = redisTemplate.opsForValue().get(id)
?: throw IllegalArgumentException("존재하지 않는 학생 정보입니다.")
// Serialize 하여 데이터 로딩
return objectMapper.readValue<Student>(studentJson)
}
}
- 데이터를 저장할 때는 opsForValue()의 set, 불러올 때는 get 메소드 사용
- opsForValue뿐 아니라 opsForList, opsForSet 등 다양한 연산 제공
- Redis의 K-V 쌍에 Key에는 student의 id, Value에는 Student 객체를 저장
- Object 타입이 아닌 String 타입이므로 객체를 Serialize하여 저장
- {id: ?, name: ?, grade: ?}같은 형식으로 저장됩니다.
- write시는 serialize, read시는 deserialize (직접 ObjectMapper을 사용합니다)
- opsForValue().get() 메소드는 nullable하므로 따로 처리(존재하지 않는 경우에는 null 리턴)
조금 더 간편하게 사용해 보자(With. Kotlin)
Redis의 get, set에만 신경쓰고 싶은 경우(serialize, deserialize를 꼭 서비스 코드 상에서 해주어야 하나?)
- redisTemplate 생성 시 defaultSerializer을 지정해 줍니다.
- Kotlin의 경우, objectMapper.registerKotlinModule()로 코틀린 모듈을 등록시켜 줍니다.
Jackson-Kotlin
- Kotlin Data Class에 대한 편의성을 제공해 줍니다.
- 기본 생성자가 따로 필요하지 않습니다.
- 데이터 클래스 생성자를 사용합니다, JSON 속성명은 kotlin의 런타임 타입 정보에 의해 추론될 수 있습니다.
- https://www.baeldung.com/kotlin/jackson-kotlin
@Bean
fun objectMapper(): ObjectMapper = ObjectMapper()
.registerKotlinModule()
@Bean
fun redisTemplate(objectMapper: ObjectMapper, redisConnectionFactory: RedisConnectionFactory): RedisTemplate<String, Student> {
val serializer = Jackson2JsonRedisSerializer(objectMapper, Student::class.java)
val template = RedisTemplate<String, Student>()
template.setConnectionFactory(redisConnectionFactory)
template.setDefaultSerializer(serializer)
return template
}
@Repository
class StudentRedisRepository(
private val redisTemplate: RedisTemplate<String, Student>,
) {
fun save(student: Student) {
redisTemplate.opsForValue().set(student.id, student)
}
fun getStudent(id: String): Student {
val student = redisTemplate.opsForValue().get(id)
?: throw IllegalArgumentException("존재하지 않는 학생 정보입니다.")
return student
}
}
이전 코드와 비교해 보았을 때, objectMapper을 직접 사용해주지 않아도 deserialize/serialize 되어 간편하게 사용할 수 있습니다.
'프레임워크 > Spring' 카테고리의 다른 글
QueryDSL 핥아만 보기 (0) | 2022.11.21 |
---|---|
Jpa의 @Query 탐험기 (0) | 2022.11.19 |
프로젝트에서의 동시성 문제 해결기(낙관적 락, 비관적 락) (0) | 2022.11.16 |
N+1 문제와 해결 방법 (0) | 2022.11.06 |
스프링 이벤트를 통한 양방향 의존성 풀기 (0) | 2022.11.04 |