
@Convert를 사용하여 List 타입을 String 타입으로 DB 저장 후 꺼내 올 때 다시 List 타입으로 변경하는 방법 학습- JPQL 쿼리로 받은 데이터베이스 조회 값 DTO에 매핑하는 방법 학습
- JPQL으로 내부 조인, 세타 조인 방법 학습
데이터 베이스에 접근해서 값을 조회하는 부분에서 많은 시간을 썼다. 결국 해결해서 기분은 좋았지만 기술적으로 타협한 느낌이라 아직 멀었다고 생각이 들었다. 그래도 이런 경험을 쌓는 거 자체로 많이 성장하고 있다고 느껴져서 뿌듯하긴 하다! 이 프로젝트를 시작한 후부터 자신감도 생기고 이제 무슨 프로젝트던 다 할 수 있을 거란 생각이 들어서 좋았던 한 주였다.
피터(사용자)의 메인 페이지에서 상위 인기 코디네이터의 정보를 조회하는 부분에서 데이터 베이스를 조회할 때 쿼리를 1개만 작성해서 원하는 값을 다 가져오는 부분이 가장 힘들었다. 그래서 이후에 데이터 베이스를 수정해서 문제를 해결하였다. 해결 방법 설명전에 어느부분에서 어려웠는지 설명하겠다.
우리가 보여주는 아우터(코디네이터)의 정보는 닉네임, 의뢰 받은 수, 선호 스타일들, 프로필 사진, 대표 게시물 사진 이렇게 구성 되어 있다. 초기 테이블 구성은 아우터 프로필(닉네임, 의뢰 받은 수, 프로필 사진) / 아우터 스타일(선호 스타일들) / 아우터 게시물(대표 게시물 사진) 으로 총 3개의 테이블에 나눠서 저장되어 있었다. 이렇게 한 이유는 데이터 베이스 시간에 배운 '정규화' 라는 개념 때문이다. 처음에 테이블 설계 시작할 때 아우터가 설정하는 스타일들을 "미니멀,스포티,시티보이" 이런식으로 문자열로 만들고 이후에 콤마를 기준으로 파싱을 해서 사용할 생각이었다. 그런데 이 방법은 제1정규형(한 컬럼에는 하나의 데이터만 있어야 한다는 뜻)에 어긋나는 방법이여서 아우터 스타일 테이블을 따로 생성하여 아우터의 기본키를 외래키로 가지게 설계하였다. 근데 이제 이 부분에서 난관에 봉착하게 되는데 위에서 말한 피터의 메인 페이지에서 저 정보들을 하나의 쿼리로는 도저히 불러 올 수 없었다... 가능은 하지만 쓸 데 없이 너무 많은 행이 결과로 오게 되었다. 만약 스타일이 총 4개이면 나머지 값(닉네임, 의뢰 수, 프로필 사진, 대표 게시물 사진)은 다 같은데 스타일 컬럼의 값만 달라서 총 16개의 데이터가 중복되어서 너무 비효율적이였다.
최대한 조인과 쿼리를 잘 짜서 해결해 보려고 했지만 무리라고 판단해서 결국 정규화를 무시하기로 결정했다. 대신 일일이 콤마로 파싱하는 거보다 좀 더 효율적인 방법을 찾아서 적용했다. 바로 @Convert 를 사용하는 방법이다.
먼저 아우터의 스타일을 받을 List 타입 변수에 @Convert를 적용시켜준다.
@Convert(converter = StringListConverter.class)
private List<String> styles = new ArrayList<>();
이후에 StringListConvert 클래스 파일을 만들고 내가 원하는 작업을 오버 라이드해서 구현하면 된다.
@Slf4j
public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
@Override
public String convertToDatabaseColumn(List<String> attribute) {
try {
return mapper.writeValueAsString(attribute);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException();
}
}
@Override
public List<String> convertToEntityAttribute(String dbData) {
TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {};
try {
return mapper.readValue(dbData, typeReference);
} catch (IOException e) {
throw new IllegalArgumentException();
}
}
}
convertToDatabaseColumn 메서드는 프론트에서 넘어오는 List 타입의 데이터를 DB에 저장할 때 문자열로 직렬화 해준다.
그리고 convertToEntityAttribute 메서드는 문자열로 저장 된 데이터를 역직렬화 해줘서 다시 List 타입으로 변환 해준다.
이렇게 하면 일일이 콤마로 나눠주고 꺼내와서 다시 파싱하고 List에 담아주는 작업을 하지 않아도 되어 효율적이게 된다.
간단하게 설명하자면 ["미니멀","스포티","시티보이"] 로 구성된 List를 DB에 저장 할 때 "미니멀","스포티","시티보이" 로 문자열로 변환 한 다음에 DB에서 꺼내와서 프론트로 넘겨줄 때 ["미니멀","스포티","시티보이"] List 타입으로 변환해서 다시 넘겨준다.
DB 에는 아래와 같이 저장된다!
추가적으로 DTO 매핑 내용은 따로 블로그를 작성해서 올릴 예정이다.
시간을 잘 분배하고 내가 해야 할 일을 명확하게 인식하고 개발을 해야 할 거 같다.
- 이미지 URL 저장 방식 변경 ( 중복 되는 부분 제거하고 사진명만 저장)
- 결제 위변조 검사 로직 만들기
- 프론트 친구들과 API 마무리
열심히 포스팅한 글 정말 잘 봤습니다! 서로이웃 신청해요~