Skip to content

트러블 슈팅

Park Hyeongmin edited this page Apr 22, 2022 · 14 revisions

형민

1. Pagenation기능 구현 - Page To Dto

JPA를 이용해서 Pageable 인터페이스 객체로 값을 불러오는데, 사용자에게 보내는 정보를 Entity 그대로 보내고 싶지않았다.

📌문제

  1. Page 객체를 List로 바꿔서 response 보내니, pageable 정보가 모두 사라지고, content만 변환되는 것을 보았다.

❓내가 원하는 것

  1. Reponse를 DTO 객체로 응답
  2. Pageable의 totalPage 정보(총 페이지(offset)수)를 클라이언트에 같이 보내고 싶다.

❓해결 고민

Controller에서 @RestController는 응답 결과를 JSON 객체로 보낸다??고 알고있기 떄문에,
Java Map 객체를 이용해서 Key: totalpage, value : List 를 return 해 주었다.

✅ 결과

2. JPA 외부참조 엔티티의 size로 정렬 - JPA 서브쿼리 사용

JPA 페이징 정렬을 사용할 때, 참조중인 엔티티의 개수로 정렬을 하고싶다!

📌문제

  1. JPA PageRequest.of(sort.by("멤버변수 이름"))으로 정렬을 할때, 멤버 변수의 이름으로 정렬이 가능하고, 외부 참조 엔티티의 값을 가지는 List 타입의 멤버변수의 이름을 넘겨주니, JPA 내부 쿼리에서 외부참조리스트의 모든 데이터값을 가져와 중복이 일어나는 현상이 발생했다.

image

❓내가 원하는 것

  1. 정렬 데이터 중복 에러 해결
  2. 참조중인 외부 엔티티의 Size로 정렬

❓해결 고민

  1. List 의 Size를 호출해주는 함수를 작성?
  2. 서브쿼리를 이용해서 Count해줄 방법을 고민

✅ 결과

1번 방법은, JPA에서 실행하는 내부쿼리를 보았을 때, Size()를 구하는 메서드를 호출 할 때마다 추가적인 Select 쿼리문이 발생하는 것을 확인하였다. 매번 정렬을 할때마다, 쿼리문을 호출하고, 엔티티의 모든 데이터를 불러오기 때문에 성능에 문제가 있을거라 생각이되었다.

  1. @Formula 어노테이션의 사용 결과적으로, 네이티브 SQL에서 서브쿼리를 사용하는 것처럼, JPA에서 가상컬럼을 만드는 방법을 찾아보았고, 하이버네이트에서 제공하는 @Formula 어노테이션을 이용해서, Count 가상컬럼을 구현할 수 있었다.

**@Formula 어노테이션은 네이티브 SQL을 사용한다.**
//좋아요 수 count 컬럼 - 가상 
@Formula("(select count(*) from post_like l where l.post_id = id)")
private int countOfLikes;

JPA 내부 쿼리 - 서브쿼리 사용을 확인!!

3. OneToMany N+1 문제 해결하기

JPA를 더 효율적으로 사용하고싶다! 주르륵 쿼리 보기싫다!

📌문제

  1. JPA를 이용해 객체 조회시 나오는 N개의 불필요한 쿼리들

❓내가 원하는 것

  1. 1개의 객체를 조회할 떄, 단 1번의 쿼리로 문제를 해결하고 싶다.

❓해결 고민

  1. N+1이 뭐제, 왜 이런 문제가 발생하지?
  2. join의 사용

✅ 결과

  1. N+1 문제의 발생 원인
  • N+1은 JPA 연관관계에 있는 객체들을 불러올 떄, 영속성 컨테이너에 저장하지 않는다.
  • 따라서, 불러온 객체를 이용해, 연관된 객체를 조회할 떄, 다시 한번 원본 객체의 PK값을 이용해 조회하게 된다.
  • 이렇게 연관된 N의 객체들을 1개씩 조회하기 떄문에, N+1의 쿼리문이 발생한다.
  1. join의 사용 -> fetch join의 사용
  • join의 사용 또한, 연관관계에 있는 객체들을 영속성 컨테이너에 저장하지않아 문제를 해결할 수 없었다.
  • Fetch join을 사용하면, join과 같은 결과가 나오지만, 영속성 컨테이너에 그 데이터들을 저장한다.
  1. ~ToMany는 BatchSize를 이용해서 해결
  • ~ToMany는 join을 사용할 수 없다. (카디널 곱이 일어나기 때문에, JPA에서 막아놈)
  • N+1 문제는, N개의 쿼리를 1개씩 불러오기 때문
  • BatchSize는, 이 1개씩 불러오는 쿼리의 사이즈를 정할수 있다.
  • 1000Size로 지정하여, 객체가 연관된 객체 1000개만 일단, 저장하도록 하여 쿼리 성능을 최적하 할 수 있었다.

4. JPA 페이징 API 성능 개선

더 빠른 성능을 이끌어내고싶다! 오프셋 보다는 커서페이징을! 카운트 쿼리의 최적화를!

📌문제

  1. JPA 페이징 API는 offset 페이징을 사용함
  2. 커서 페이징이 효율이 더 좋은 걸로 알고있음

❓내가 원하는 것

  1. JPA 오프셋 페이징을 커서 페이징으로 전환할 것
  2. 최적화할 수 있으면 최적화 할 것

❓해결 고민

  1. JPA 페이징 API가 아닌 JPQL을 사용하여 내가 직접 커스텀을 해주어야하나?
  2. QueryDSL을 사용하면 효율이 더 좋아질 까?

✅ 결과

  1. 정렬하는 부분을 동적으로 처리해 주고 싶었기 때문에, QueryDSL로 튜닝을 시도
  2. 동적 정렬과 마지막 게시물의 인덱스를 넘겨주어 커서페이징으로 튜닝 시도 했지만 실패
  • 2페이지 이상 이동 시 문제
  • index값이 일정하지않기 때문
  1. queryDSL로 오프셋 방식의 정렬 진행, 카운트 쿼리 분리 최적화



송연

1.AWS S3를 사용한 파일 업로더 구현 - 이미지 파일은 Entity, Ajax, 서버 어떻게 관리하면 좋을까?

File로 받아서 String으로 저장

📌문제

  1. input type="file" 로 이미지 파일을 업로드하면 JSON으로 통신 실패
  2. Multipartfile로 받아서 AWS에 저장 후 어떻게 출력 할지 고민
  3. 이미지 업로드 없이 제목, 내용만 저장하고 싶을때 500 error

❓ 내가 원하는 것

  1. AWS에만 파일 저장해서, 필요할때 불러오고 싶음
  2. 제목과 내용만 저장 하기

❓ 해결 시도 Action

  • 이미지 파일 통신하기
  1. 클라이언트와 서버 사이 어떤 값이 어떻게 통신 하는지 확인
  2. 이미지 파일이 들어가는 순간 JSON 값이 서버로 안넘어가는 것을 확인
  3. 구글링으로 파일은 form으로 보낼수 있는 것을 확인
  4. @RequestBody 말고 JS에서 FormData() 객체에 담아서 컨트롤러: @ModelAttribute로 받아서 form으로 통신
  5. 서버로 넘어온 파일을 AWS s3에 저장 후에 return 이미지 url, 그리고 DB 저장
  6. DB에 image url을 "image src='#'"에 넣어서 화면 출력
  • 제목, 내용만 저장하기
  1. null 값 처리 -> input type="file" 을 사용하지 않으면 기본적으로 null 값을 할당함
  2. 분기 처리로 값이 없으면 FormData()에 들어가지 않도록 처리
  3. 서버에서도 file 값이 있을 경우에만 AWS S3 저장 기능이 작동하도록 분기처리
  4. 제목, 내용만 nullable=false 처리하여 제목, 내용으로만 게시글 작성하도록 함
  5. 제목이나 내용 없이 통신했을 경우 postman으로 TEST 했을때 500 error (클라이언트 잘못인데 왜 500?)
  6. Validation 라이브러리를 받아서 CommonErrorAdvice를 만들어 ajax Erorr와 message를 사용해 400으로 변환

✅ 결과

  1. Form으로 data를 보내서 AWS S3에 저장
  2. 조건문을 사용해서 이미지 파일이 올 경우만 저장
  3. 필수조건을 충족하지 않으면 ErrorAdvice가 처리