[F-Lab 모각코 챌린지 64일차] JSON null 처리, Pagination + FetchJoin

부추·2023년 8월 3일
0

F-Lab 모각코 챌린지

목록 보기
64/66

JSON으로 나가는 DTO에 null값 항목 없애기

DTO를 최대한 줄이는 작업을 하고있다. 유저 Request를 제외하고는 어플리케이션을 돌아다니는 DTO는 하나의 엔티티당 2개 이내로 유지하려고 하고 있다.

그러나 user response 응답으로 내려줄 DTO가 문제였다. 유저의 id만 필요할 때가 있고, 유저의 이름과 id만 필요할 때가 있고, 팔로우 팔로워 수까지 전부 필요할 때가 있었다. 이 때마다 DTO를 새로 만들어주는건 심히 비효율적이다. 객체에서 응답으로 내려줄 필요가 없는 필드엔 null을 만들어서 응답으로 내려주려고 하는데, 응답 JSON 본문에 null이 있는건..!!!! 용납 안 돼!!


DTO 항목이 null일 때 JSON에서 무시하도록 만들 수 있을까?

당연히 갓 개발자님들은 해결법을 이미 만들어놓으셨다.

@Getter
@JsonInclude(JsonInclude.Include.NON_NULL) // Null 값인 필드 제외
@Builder
public class ResponseDto {
	private Long userId;
    private String email;
    private String nickname;
    
    // fromEntity 등
}

이제 DTO에 원하는 필드만을 설정해서 사용자 응답으로 내려줄 수 있게 되었다. 위의 예시에서 사용자 이메일만 응답으로 내려주고 싶으면 userIdnickname을 null로 두면 response body는 아래와 같은 JSON 형식이 될 것이다.

{
  "email" : "buchu@email.com"
}



Pagination + FetchJoin

.offset, .limit을 이용하여 페이지네이션을 진행하는 querydsl 쿼리를 작성했다. Pageable 객체를 이용했다. N+1 문제 해결을 위해 연관 관계 엔티티를 fetchJoin까지 한 상황이다.

List<FarmLog> farmLogs = jpaQueryFactory
            .selectFrom(farmLog)
            .leftJoin(farmLog.likers).fetchJoin() // 좋아요한 사람들 fetch join
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(farmLog.createdAt.desc())
            .fetch();

일단 쿼리 결과는 나오지만?

firstResult/maxResults specified with collection fetch; applying in memory

WARN이 뜬다.

일대다 관계가 여러 개인 상황에서 fetch join을 했을 때와 비슷하게, 쿼리 결과 전체를 메모리에 올려놓은 뒤 pagination을 진행해서 그런듯. 실제로 hibernate에서 나간 쿼리문을 보면 offset이나 limit 키워드는 전혀 보이지 않는다.

fetchJoin이 있을 때 query parameter가 hibernate 쿼리문에 null값으로 들어가는 듯하다. 쿼리문 자체에 offset + limit이 들어가는게 아니라 전체 결과를 메모리에 올려 어플리케이션 단계에서 paging을 진행하고 있다, 따라서 메모리 오버가 발생할 위험성이 있다.. 라는 경고이다.


# 해결법 : 쿼리 나누기

// limit 절만 사용할 query 따로 뺌
List<Long> farmLogIds = jpaQueryFactory.select(farmLog.farmLogId)
            .from(farmLog)
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(farmLog.createdAt.desc())
            .fetch();

// fetchJoin
List<FarmLog> farmLogs = jpaQueryFactory.selectFrom(farmLog)
            .leftJoin(farmLog.likers).fetchJoin()
            .where(farmLog.farmLogId.in(farmLogIds))
            .fetch();
  1. pagination 적용해서 엔티티 id만 뽑아오는 쿼리
  2. 해당 id 객체를 가져와 fetchJoin하는 쿼리

둘을 나눠서 결과 result를 구했다. 같은 요청에 대해서 사실상 같은 쿼리가 두 번 나가는 일이다...

"2번 쿼리 날리기 vs N+1 겪기"

무엇을 포기하냐에 따라 다를듯.



REFERNCE

https://bestinu.tistory.com/45
https://javabom.tistory.com/104

profile
부추튀김인지 부추전일지 모를 정도로 빠싹한 부추전을 먹을래

0개의 댓글