상황

Redis Cache 를 사용해서 List<?> 를 저장하려고 했습니다.

직렬화해서 데이터 저장까지는 잘 되었는데 다시 역직렬화 하려고 하니 에러가 발생하며 실패했습니다.


@Cacheable(cacheNames = "members", key = "'all'")
public List<Member> findAll() {
    List<Member> members = store.values().stream().toList();
    return members;
}

캐싱한 데이터는 위와 같습니다.

List<Member> 를 응답으로 내려주고 members 라는 캐시의 all 이라는 키값으로 저장됩니다.

Redis 설정으로 Value 는 GenericJackson2JsonRedisSerializer 를 사용하여 직렬화했습니다.


Redis 를 확인해보면 제대로 저장된 것을 확인할 수 있습니다.


에러 로그

com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected VALUE_STRING: need JSON String, Number of Boolean that contains type id (for subtype of java.lang.Object)
 at [Source: (byte[])"[{"@class":"com.example.springbootcache.model.Member","id":1,"name":"ChulSoo","age":50}]"; line: 1, column: 2]

원인

List<?> 를 그대로 저장해서 그렇습니다.

사실 정확한 원인은 저도 모릅니다.

그래도 확신은 없지만 나름대로 추측을 해보겠습니다.

GenericJackson2JsonRedisSerializer 는 직렬화 할 때 @class 라는 Key 값에 클래스의 패키지 정보까지 전부 저장됩니다.

그런데 List 를 통째로 저장하면 위 사진과 같이 { "@class": "..." } 이 아니라 [{ "@class": "..."}] 로 저장되어 찾지 못해서 발생하는 이슈 같습니다.


해결

List 를 감싸는 Wrapper 클래스를 만들어 주면 해결됩니다.


Members 클래스 정의

@Getter
@NoArgsConstructor
public class Members {
    private List<Member> members = new ArrayList<>();

    public Members(List<Member> members) {
        this.members = members;
    }
}

캐싱 대상 변경

@Cacheable(cacheNames = "members", key = "'all'")
public Members findAll() {
    List<Member> members = store.values().stream().toList();
    return new Members(members);
}

Redis 저장 확인

Issue

Ruby Grape 와 Grape Entity 를 사용할 때 클래스를 참조하지 못하고 자꾸 NameError (uninitialized constant {Class}) 에러가 발생했습니다.

처음에는 Rails 버전과 Grape 버전이 호환되지 않아서 발생하는 이슈인가 해서 다운그레이드도 해봤지만 해결되지 않았습니다.


Solution

제가 이런 오류를 겪게된 이유는 크게 두가지였는데요.

첫번째는 제가 Ruby 에 익숙하지 않아서 발생한 일이고 다른 하나는 Grape 문서를 제대로 읽지 않아서 발생했습니다.


1. Ruby File 형식을 준수하고 파일 이름과 Class 이름이 일치해야 함

SimpleResponseEntity 라는 클래스를 사용한다고 하면 파일 이름을 simple_response_entity.rb 로 만들어야 합니다.

무심코 Java 에서의 버릇처럼 Camel Case 로 작성했던게 문제였습니다.


2. Grape 프레임워크는 Module 과 파일의 경로가 일치해야 함

Ruby Grape Docs > Mounting > Rails 파트를 보면 다음과 같은 문장이 있습니다.

Place API files into app/api. Rails expects a subdirectory that matches the name of the Ruby module and a file name that matches the name of the class. In our example, the file name location and directory for Twitter::API should be app/api/twitter/api.rb.


즉, Grape 관련된 기능을 사용하기 위해선 파일을 app/api 하위에 만들어야 하며 subdirectory 와 module 이름까지 일치해야 한다는 뜻입니다.

Rails 자체도 파일이나 경로를 굉장히 중요시하는 것처럼 Grape 도 비슷한 특징을 갖고 있는 것 같습니다.


Reference

Issue

Using default tag: latest
The push refers to repository [docker.io/asdfaasdf/kubia]
6bb2a0932f1d: Preparing 
ab90d83fa34a: Preparing 
8ee318e54723: Preparing 
e6695624484e: Preparing 
da59b99bbd3b: Preparing 
5616a6292c16: Waiting 
f3ed6cb59ab0: Waiting 
654f45ecb7e3: Waiting 
2c40c66f7667: Waiting 
denied: requested access to the resource is denied

Docker Hub 에 이미지를 푸시하려려고 하는 데 아래와 같은 에러 로그가 나타났습니다.

로그를 보면 알 수 있듯이 권한 관련된 이슈인데 이유는 크게 두 가지가 있습니다.

  1. Docker Hub 로그인을 하지 않음
  2. Docker Hub 아이디와 태그된 이미지의 이름이 일치하지 않음

Solution

우선 Docker Hub 에 로그인합니다.

$ docker login           
Login with your Docker ID to push and pull images from Docker Hub. If you dont have a Docker ID, head over to https://hub.docker.com to create one.
Username: bcp0109
Password: 
Login Succeeded

Docker Tag 를 Docker Hub ID 와 동일하게 생성합니다.

$ docker tag kubia bcp0109/kubia

이미지를 푸시합니다.

$ docker push bcp0109/kubia     
Using default tag: latest
The push refers to repository [docker.io/bcp0109/kubia]
6bb2a0932f1d: Pushed 
ab90d83fa34a: Pushed 
8ee318e54723: Pushed 
e6695624484e: Pushed 
da59b99bbd3b: Pushed 
5616a6292c16: Pushed 
f3ed6cb59ab0: Pushed 
654f45ecb7e3: Pushed 
2c40c66f7667: Pushed 

Docker Hub 에 이미지가 올라간 걸 확인할 수 있습니다.

Issue

웹 페이지를 만들 때 고려해야 할 점 중 하나가 시각 장애인을 위한 웹 접근성입니다. (웹 접근성 포스트)

웹 접근성을 적용할 때 iOS 웹뷰에서 실시간으로 변하는 타이머에 초점이 잡히지 않는 이슈가 있었습니다.

처음에는 aria-live 속성을 적용해서 실시간 보이스 낭독을 기대했지만 문장을 다 읽기 전에 시간이 계속 바뀌어서 말이 끊기는 이슈가 있었습니다.

그래서 처음 초점 잡혔을 때의 시간만 안내하길 기대하면서 role="timer" 를 적용했지만 아예 초점이 잡히지 않았습니다.


Solution

답은 role="text" 를 적용하는 거였습니다.

role="text" 는 WAI-ARIA 공식 홈페이지에서도 설명이 없는 걸로 보아 정식 role 은 아닌 것 같습니다.

다만 이 role 만 적용하면 어떤 역할인지 보이스 안내만으론 알기 힘들기 때문에 aria-describedby 속성까지 추가하는 것을 추천합니다.

<span class="screen_out" id="timer_label" aria-hidden="true">남은 시간</span>
<div role="text" aria-describedby="timer_label">10:00</div>

에러 로그

$ yarn start  
yarn run v1.22.17
$ react-scripts start
node:internal/modules/cjs/loader:488
      throw e;
      ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './lib/tokenize' is not defined by "exports" in /Users/Documents/my/projects/practice-codes/react-js-hello/node_modules/postcss-safe-parser/node_modules/postcss/package.json
    at new NodeError (node:internal/errors:371:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:416:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:669:3)
    at resolveExports (node:internal/modules/cjs/loader:482:36)
    at Function.Module._findPath (node:internal/modules/cjs/loader:522:31)
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
    at Function.Module._load (node:internal/modules/cjs/loader:778:27)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/Users/Documents/my/projects/practice-codes/react-js-hello/node_modules/postcss-safe-parser/lib/safe-parser.js:1:17) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Node.js v17.0.1
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

원인

Mac OS 를 BigSur 로 업그레이드 후 연습중이던 React 프로젝트를 클론받아 실행했더니 에러가 발생했습니다.

복잡한 프로젝트도 아니어서 당황하던 중 검색해보니 node 17 버전을 사용해서 그렇다는 글을 봤습니다.

현재 제 노드 버전을 확인해보니 v17.0.1 을 사용하고 있었습니다.


해결

nvm (Node Version Manager) 을 설치한 후에 노드 버전을 다운그레이드 해서 해결했습니다.

처음에 brew install nvm 으로 설치했는데 잘 안되어서 nvm Github 을 보고 다시 설치했습니다.

# nvm 설치
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

설치 후에 ~/.zshrc 을 열어 맨 아래에 다음 내용을 붙여넣습니다.

기본 터미널을 bash 로 사용하고 있다면 ~/.bash_profile 에 추가하면 됩니다.

export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

마지막으로 ~/.zshrc 를 갱신한 뒤 LTS 버전을 설치하고 다시 실행해보면 정상적으로 동작합니다.

# nvm command 가 안뜨면 갱신
$ source ~/.zshrc

# nvm 설치 확인
$ nvm -v
0.39.0

# node lts 버전 설치
$ nvm install --lts

# 16 버전이 설치됨
$ node -v
v16.13.0

Reference

에러 로그

TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing

원인

JPA 연관 관계 테스트 중에 발생했습니다.

FK 로 사용되는 컬럼값이 없는 상태에서 데이터를 넣으려다 발생한 에러입니다.

예를 들어 Person (id, name) 이라는 테이블과 House (id, address, person_id) 라는 테이블 관계가 있을 때, Person 데이터를 넣지 않고 House 데이터를 넣으려고 하면 person_id 값이 없어서 에러가 발생합니다.

Person person = new Person("Alice");
House house = new House("Seoul", person);

houseRepository.save(house);    // 에러 발생 (person 의 id 값을 모르는데 테이블에 넣으려고 해서)

해결 방법

연관 관계 매핑해줄 때 사용하는 @ManyToOne, @OneToOne, @OneToMany 어노테이션에 cascade 옵션을 설정해줍니다.

cascade 는 "영속성 전이" 라고 하는 개념인데 특정 엔티티를 영속화 할 때 연관된 엔티티도 함께 영속화 합니다.

저장할 때만 사용하려면 cascade = CascadeType.PERSIST 로 설정해주면 되며, 전체 적용인 CascadeType.ALL 로 설정해도 됩니다.

그럼 House 데이터를 저장하기 전에 Person 값부터 저장합니다.


Reference

해결하고 나니 굉장히 사소한 실수였습니다.

Error creating bean with name 'tokenRedisRepository' defined in TokenRedisRepository defined in @EnableRedisRepositories declared on RedisRepositoriesRegistrar.EnableRedisRepositoriesConfiguration: 
Invocation of init method failed; 
nested exception is org.springframework.data.mapping.MappingException: Entity com.example.login.entity.
Token requires to have an explicit id field. Did you forget to provide one using @Id?
  • RedisRepository 를 적용하는 도중 위와 같은 에러가 발생
  • Redis 에 사용할 객체의 @Id 어노테이션 임포트를 잘못해서 발생 (JPA 에서 사용하는 @Id 를 임포트함)
  • javax.persistence.Id 대신 org.springframework.data.annotation.Id 을 임포트 해서 해결

+ Recent posts