Redis 캐스팅 오류

코코코딩을 합시다·2024년 8월 9일

Redis 에 hashtagId와 userId를 String, Object 타입으로 삽입하고 읽어오는 과정에서 캐스팅 오류가 발생했다.

java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String

기존 코드는 다음과 같다.

수정 전

private final RedisTemplate<String, Object> redisTemplate;

public void set(Long hashtagId, Long userId) {
    String key = "hashtagId:" + hashtagId.toString();
    redisTemplate.opsForList().rightPush(key, userId);
}

public List<Follow> getAllFollows() {
    Set<String> keys = redisTemplate.keys("hashtagId:*");
    List<Follow> follows = new ArrayList<>();
    if (keys != null) {
        for (String key : keys) {

            List<Object> userIds = redisTemplate.opsForList().range(key, 0, -1);
            if (userIds != null) {
                for (Object userId : userIds) {
                    Long hashtagId = Long.parseLong(key.split(":")[1]);
                    User user = userRepository.findById((Long)userId)
                            .orElseThrow(() -> new IllegalArgumentException("해당 사용자를 찾을 수 없습니다."));
                    Hashtag hashtag = hashtagRepository.findById(hashtagId)
                            .orElseThrow(() -> new IllegalArgumentException("해당 해시태그 찾을 수 없습니다. "));
                    Follow follow = new Follow(user, hashtag);
                    follows.add(follow);
                }
            }
        }
    }
    return follows;
}

원인은 userId를 바로 (Long)으로 캐스팅하려 해서 발생한 문제였다.

Redis 는 기본적으로 데이터를 문자열로 취급해서 내부적으로 바이트 배열로 변환되어 저장된다. 따라서 Long 으로 저장해도 String으로 저장된다.

따라서 읽어올 때 String 으로 읽어온 뒤 원하는 데이터로 캐스팅해줘야 한다.

수정 후

public void set(Long hashtagId, Long userId) {
    String key = "hashtagId:" + hashtagId.toString();
    redisTemplate.opsForList().rightPush(key, userId);
}
public List<Follow> getAllFollows() {
  Set<String> keys = redisTemplate.keys("hashtagId:*");
  List<Follow> follows = new ArrayList<>();
  if (keys != null) {
      for (String key : keys) {

          List<Object> userIds = redisTemplate.opsForList().range(key, 0, -1);
          if (userIds != null) {
              for (Object userId : userIds) {
                  Long hashtagId = Long.parseLong(key.split(":")[1]);
                  Long userIdLong = Long.parseLong(String.valueOf(userId));

                  User user = userRepository.findById(userIdLong)
                          .orElseThrow(() -> new IllegalArgumentException("해당 사용자를 찾을 수 없습니다."));
                  Hashtag hashtag = hashtagRepository.findById(hashtagId)
                          .orElseThrow(() -> new IllegalArgumentException("해당 해시태그 찾을 수 없습니다. "));
                  Follow follow = new Follow(user, hashtag);
                  follows.add(follow);
              }
          }
      }
  }
  return follows;
}

생각해볼 점

오류 수정하는데 되게 오래 걸렸는데.. 찾아보는 과정에서 Redis는 직렬화 문제와 밀접하다는 것을 알게 되었다. 나도 String으로 저장했을 때 값이 "/"1"/"과 같이 저장되는 것을 확인할 수 있었다. 앞서 말한대로 Redis의 모든 데이터는 byte 코드로 저장되는 것과 연관관계가 있다.

StringRedisSerializer

Redis에는 직렬화를 지원해주는 인터페이스가 많은데 그 중 StringRedisSerializer이 보편적으로 많이 사용된다.

StringRedisSerializer는 String 값을 그대로 저장하는 Serializer로, 직접 인코딩/디코딩을 해줘야 하는 번거로움이 있지만 class 타입을 지정할 필요가 없고, redisTemplate을 여러 쓰레드에서 접근해도 serializer 문제가 발생하지 않아 가장 무난하다.

profile
좋아하는 걸로 밥 벌어먹기

0개의 댓글