프로젝트/개인 프로젝트(2023.11.13-2023.12.18)

[개인프로젝트] Redis 역직렬화 문제

dal_been 2023. 12. 16. 16:22
728x90

사람은 또 실수를 반복하지 아무 생각없이 직렬화관련해서 복붙했다가 테스트하니 바로 에러나는거 보소... 하하하하 역시 공부한다음에 복붙을 하든 해야해...아무것도 모르고 사용하니까 당연히 에러 나오지...

 

https://haebing.tistory.com/104

 

이전에 Redis 직렬화에 대해서 적어둔것이다.

 

문제상황

 

일단 나의 RedisConfig는

 

@Configuration
public class RedisConfig {

  @Value("${spring.data.redis.host}")
  private String redisHost;

  @Value("${spring.data.redis.port}")
  private int redisPort;

  private static final String REDISSON_HOST_PREFIX="redis://";

  @Bean
  public RedissonClient redissonClient(){
    Config config=new Config();
    config.useSingleServer().setAddress(REDISSON_HOST_PREFIX+redisHost+":"+redisPort);
    return Redisson.create(config);
  }

  @Bean
  public RedisConnectionFactory redisConnectionFactory(){
    return new LettuceConnectionFactory(redisHost,redisPort);
  }

  @Bean
  public RedisTemplate<?,?> redisTemplate(){
    RedisTemplate<byte[], byte[]> redisTemplate=new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory());
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new StringRedisSerializer());
    return redisTemplate;
  }


}


로 StringRedisSerializer를 이용했다.

 

그래서 간단하게 테스트로 

  @Test
  void setData() {
    String key=startServicePrefix+1;
    String value="ON";
    int timeUnit=30;
    redisTemplate.opsForValue().set(key,value,timeUnit);

    Object isStarted = redisTemplate.opsForValue().get(key);
    if(isStarted !=null){
      System.out.println(String.valueOf(isStarted));
    }
    redisTemplate.delete(key);
  }

 

이렇게 했다. 결과는 ON이 나와야 했지만 

이게뭐죠..??예?? 앞에 있는 거는 복사도 안됬다..

 

거기다가

  @Test
  void addToList() {
    String key=proceedServicePrefix+1;
    Coordinate coordinate1=new Coordinate(12.0,3.0);
    Coordinate coordinate2=new Coordinate(15.0,12.0);
    redisTemplate.opsForList().rightPush(key,coordinate1);
    redisTemplate.opsForList().rightPush(key,coordinate2);

    RedisOperations <String, Object> operations = redisTemplate.opsForList().getOperations();
    List <Object> list = operations.opsForList().range(key , 0 , -1);
    List <Coordinate> collect = list.stream()
        .filter(obj -> obj instanceof Coordinate)
        .map(obj -> (Coordinate) obj)
        .collect(Collectors.toList());
    for (Coordinate coordinate : collect) {
      System.out.println(coordinate);
    }
  }

 

Coordinate를 저장해야하는 데 iter돌려보니

java.lang.ClassCastException: class org.locationtech.jts.geom.Coordinate cannot be cast to class java.lang.String (org.locationtech.jts.geom.Coordinate is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')

	at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36)
	at org.springframework.data.redis.core.AbstractOperations.rawValue(AbstractOperations.java:128)
	at org.springframework.data.redis.core.DefaultListOperations.rightPush(DefaultListOperations.java:207)
	at com.project.dogwalker.common.service.RedisServiceTest.addToList(RedisServiceTest.java:46)

 

네 cast안된데요.... 예??예??

 

해결..??

 

이거는 일단 직렬화, 역직렬화의 문제라고 생각했다. cast자체가 안된다는 점은...

그래서 redisConfig수정에 들어갔다.

 

  @Bean
  public RedisTemplate<String,Object> redisTemplate(){
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    RedisSerializer <Object> jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

    RedisTemplate<String, Object > redisTemplate=new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory());
    redisTemplate.setKeySerializer(jsonSerializer);
    redisTemplate.setValueSerializer(jsonSerializer);
    return redisTemplate;
  }

 

이렇게하였다. FAIL_ON_UNKNOWN_PROPERTIES는 역직렬화시 맞는 필드가 없으면 실패시킬것인지에 대한 속성이다. 일단는 false로 해둔 상태로 돌렸는데 어찌어찌 ClasscastException은 넘어갔는데.. 다른 문제가 발생했다...

 

 


문제상황

 

java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Invalid ordinate index: 3
at [Source: (String)"{"x":12.0,"y":3.0,"z":"NaN","valid":true,"m":"NaN"}"; line: 1, column: 46] (through reference chain: org.locationtech.jts.geom.Coordinate["m"])

 

정말 너무해..하나 해결했더니 또 에러나오고..

보니 "m"이  mapping이 잘안되는 것같다.

근데 Coordinate클래스를 좀 살펴보자

 

public class Coordinate implements Comparable<Coordinate>, Cloneable, Serializable {
  
      public static final int M = 3;

        /**
       *  Retrieves the value of the measure, if present.
       *  If no measure value is present returns <tt>NaN</tt>.
       *  
       *  @return the value of the measure, or <tt>NaN</tt>
       */    
      public double getM() {
        return Double.NaN;     
      }

      /**
       * Sets the measure value, if supported.
       * 
       * @param m the value to set as M
       */
      public void setM(double m) {
        throw new IllegalArgumentException("Invalid ordinate index: " + M);
      }

  }

 

 

생각해보면 M는 int이기에 "NAN"이라는 것을 대입하는게 불가능하다. 뿐만아니라 static final로 선언되어있다...

거기다가 역직렬화 입장에서 setter를 이용할 수도 있는데  setM자체가 exception을 터트린다.

이러니 당연히 에러가 터진것이다.

 

근데 나는 M자체를 "NAN"으로 수정한적이 없는데 왜 그런걸까??

추측해보자면 직렬화/역직렬화시 기본생성자가 필요하다. 근데 Coordinate 생성자를 보면 x,y,z 필드만을 이용하고 있다. 그래서 직렬화시 m이 "NAN"으로 들어간것같다.

 

  public Coordinate(double x, double y, double z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  /**
   *  Constructs a <code>Coordinate</code> at (0,0,NaN).
   */
  public Coordinate() {
    this(0.0, 0.0);
  }

  /**
   *  Constructs a <code>Coordinate</code> having the same (x,y,z) values as
   *  <code>other</code>.
   *
   *@param  c  the <code>Coordinate</code> to copy.
   */
  public Coordinate(Coordinate c) {
    this(c.x, c.y, c.getZ());
  }

  /**
   *  Constructs a <code>Coordinate</code> at (x,y,NaN).
   *
   *@param  x  the x-value
   *@param  y  the y-value
   */
  public Coordinate(double x, double y) {
    this(x, y, NULL_ORDINATE);
  }

 

 

 

해결방법

 

내가 역직렬화 클래스 만들기

라이브러리는 내가 수정불가능하니 만들어야지....

 

public class CoordinateDeserializer extends StdDeserializer<Coordinate> {

  public CoordinateDeserializer(Class <?> vc) {
    super(vc);
  }

  @Override
  public Coordinate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
    JsonNode node = jp.getCodec().readTree(jp);
    double x = node.get("x").asDouble();
    double y = node.get("y").asDouble();
    return new Coordinate(x, y);
  }
}

 

StdDeserializer를 상속하면 직렬, 역직렬 변환기를 만들 수 있다.

 

이후 objectMapper에 등록해주면 되다

 

  @Bean
  public RedisTemplate<String,Object> redisTemplate(){
    SimpleModule module = new SimpleModule();
    module.addDeserializer(Coordinate.class, new CoordinateDeserializer(Coordinate.class));
    objectMapper.registerModule(module);
    RedisSerializer <Object> jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

    RedisTemplate<String, Object > redisTemplate=new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory());
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(jsonSerializer);
    return redisTemplate;
  }

 

이후 테스트 돌려보면 

 

(12.0, 3.0, NaN)
(15.0, 12.0, NaN)

 

매우 잘나온다!!! 와 성공이닷!!

 

사실 또다른 해결방법은 dto로 redis에 저장한다음에 redis에서 꺼내고 rdb(mysql)에 저장할때 다시 Coordinate로 바꿔서  LineString으로 만드는 방법이 있는데 조금 복잡하다. 변환하고 또 변환하는게 비효율적으로 느껴진다.

 


근데 당신 저 setData는 어떻게 해결할꺼야??

 

"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000ON"

 

해결방법

 

\u000를 찾아보니 공백이라고 하는 것같다. 그럼 데이터를 가져올때 공백이 생기는 건가??

아니다 저장될때 저렇게 저장된다.

 

 

왜 저렇게 저장되는 것까지는 잘 모르겠지만 공백이라면 trim()을 해주면되는 부분 아닌가?? 그래서 코드 추가 했다

 

  public boolean getStartData(final String key){
    final Object isStarted = stringRedisTemplate.opsForValue().get(key);
    if(isStarted !=null){
      String start = isStarted.toString().trim();
      return start.equals("ON");
    }
    return false;
  }

 

String으로 변환한후 trim()해주는 것이다.

 

그랬더니 테스트 성공....!!

 


오늘도 이거 해결하느라 6시간 걸렸다.. 뭐랄까... 왜 저러는지 모르겠고 직렬화도 정확하게 모르니 차근차근 찾다보니 좀 걸린듯... 그래도 뿌듯! 내 혼자 힘으로 해결했다는 점에서!

추가적으로 직렬화 관련하여 좋은 블로그를 찾았다. 같이 보면 좋을 듯!

 

https://cl8d.tistory.com/66