안녕하세요. 스프링부트 프로젝트의 RedisTemplate 에 대해 알아 보겠습니다.
Redis는 구동 되고 있다는 가정하에 작성하였습니다.
1. 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
2. RedisTemplate 설정 추가
2-1 yaml 파일에 설정 정의만 추가
스프링 부트에 yaml 파일에 정의만 해주면 자동으로 생성됩니다.
spring:
redis:
host: localhost
port: 6379
이외에도 옵션이 많은데 참고 사이트 링크로 대체하겠습니다.
위와 같이 설정이 추가된 상태이면 RedisTemplate 빈을 주입받을 수 있습니다,
@Autowired
private RedisTemplate redisTemplate;
이 상태에서 레디스에 저장된 내용을 살펴보겠습니다.
제가 redisTemplate을 사용하여 key가 3으로 저장하였는데, 3으로 값을 조회하면 값이 나오지 않습니다. key를 조회해보면 이상한 문자값으로 저장된 것을 볼 수 있습니다.
해당 키로 조회하는경우 결과도 마찬가지 입니다. 이것은 redis에 저장할때 직렬화(serialize)/역직렬화시 JDK 방식으로 진행되기 때문입니다.
2-2 RedisConfig 설정 추가하기
앞의 문제를 해결하기 위해서 직접 설정 커스텀하여 추가할 수 있습니다.
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableRedisRepositories
@Slf4j
public class RedisConfig {
@Autowired
private RedisProperties redisProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
log.info("host === {}, port === {}", redisProperties.getHost(), redisProperties.getPort());
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 이것을 추가 안하면 key가 byte로 저장이 됨
// redisTemplate 과 cacheManager 모두에 설정을 추가해야 동일한 변수를 받을 수 있음
// keySerializer를 한쪽에만 추가 하면 서로의 변수 조회시 조회가 안됨
//redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
// 이것을 추가하면
// 데이터를 읽을때 java.util.LinkedHashMap 형식으로 읽어짐 (저장할때 클래스 지정을 안하는듯)
// 목록인 경우 java.util.ArrayList 로 들어감
//redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 위와 같이 지정하면 읽었을때 class형태의 객체로 읽어짐
// 패키지 이동하면 오류가 발생
return redisTemplate;
}
}
@EnableRedisRepositories를 선언하면 앞의 2-1에서 선언한 설정을 RedisProperties를 통해 읽어 올 수 있습니다. 이것을 사용하여 RedisConnectionFactory를 정의 합니다. 위의 예제는 host와 port만 정의하였고, Redis Client 중 Lettue를 사용 했습니다.
키가 String형으로 저장 될 수 있도록 redisTemplate.setKeySerializer 를 추가합니다.
값의 직렬화 방식을 지정해야하는데 2가지를 테스트 했습니다.
2-2-1 Jackson2JsonRedisSerializer<Object>
데이터를 읽을 때 클래스가 아닌 map으로 데이터가 읽어 집니다.
key를 1로 데이터를 추가했습니다.
이번에는 key가 1로 저장 된 것을 볼 수 있습니다. 조회 데이터가 잘 확인되는것도 볼 수 있어요.
자바에서 데이터가 조회된 것을 보면 LinkedHashMap으로 읽어지고 있어요. List 형식인 경우 ArrayList로 읽어집니다. 리스트 안은 LinkedHashMap이고요.
타입을 일일히 지정하는 방법이 있기는 하지만 모두 정의해서 사용하기 쉽지 않아요.
2-2-2 GenericJackson2JsonRedisSerializer
위와 같이 정의하면 데이터를 저장할때 class 정보가 함께 저장되고, 해당 클래스로 데이터가 읽어 집니다.
Book 클래스로 바로 읽어진 것을 확인할 수 있습니다.
내용만 보면 2번의 경우가 더 좋아 보입니다. 하지만 패키지가 이동하거나 프로젝트가 많은 MSA 환경에서는 다양한 변수가 있기 때문에 클래스를 저장해서 사용하는 것이 좋다고 말하기 어렵습니다.
앞에서 발생하는 문제를 해결하기 위한 방법으로 json으로 변환된 문자열(스트링)을 저장하고, 읽은 후에 다시 클래스로 변환하는 방법이 있습니다.
redisTemplate.opsForValue().set(book.getId().toString(), gson.toJson(book));
예제는 gson 라이브러리를 사용하여 json으로 변환하여 book 객체를 저장했습니다.
json 문자열로 저장된것을 확인할 수 있습니다.
레디스에서 읽어온 문자열을 gson을 통해 클래스로 변환했습니다.
의존성 추가
implementation 'com.google.code.gson:gson:2.9.1'
유틸 클래스
import com.google.gson.Gson;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.Optional;
@Service
public class RedisHelper {
@Autowired
private Gson gson;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, gson.toJson(value));
}
public <T> Optional<T> get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
if (Objects.isNull(value) || StringUtils.isBlank(value.toString())) {
return Optional.empty();
}
T result = gson.fromJson(value.toString(), clazz);
return Optional.ofNullable(result);
}
}
데이터를 저장하고, 불러오는 기본 예제 코드 입니다. key-value 쌍으로만 동작하는 예제를 응용하여 다양하게 확장해 볼 수 있을 것 입니다.
내가 원하는 바를 개발하는데 있어 1가지 방법만 있는것이 아니라, 무수히 많은 방법이 있습니다. 자신이 개발하는 서비스의 성격에 맞는 방법을 찾는 것이 좋은 개발자가 되는 길이라 생각합니다. 잔소리 죄송합니다.
다음편은 RedisCache 로 이어집니다.
댓글
댓글 쓰기