본문 바로가기

Trouble Shooting/Data API

Querydsl 적용과 고찰

 

이번에 진행하고 있는 프로젝트에서 querydsl을 적용해보고 있다.

발생하는 고찰들에 대해서 정리하고자 한다.

 

 

1. Query DSL을 통해 불필요한 데이터 제거 및 DB내의 데이터 순환 효율성 처리

 

로그인 API

- 로그인의 기존 로직은 해당 아이디에 맞는 Entity를 찾고 해당 엔터티가 null이면 아이디가 존재하지 않음으로 보고 오류를 발생시킨다. 그리고 기존의 받은 비밀번호와 해당 아이디 엔터티에 해당하는 비밀번호가 일치하지 않으면 또 오류를 발생시킨다.

 

- 고찰 : querydsl의 장점인 가독성 부분에 대해서는 문제가 없어보인다. findByUserId라는 메서드는 충분히 이해가 가능하다. 다만, 필요없는 데이터도 가져온다. 현재 아래에서 필요한 데이터는 비밀번호 데이터 뿐인데, Entity 자체를 가져온다. 따라서, 비밀번호만 가져오는 코드로 수정할 필요가 있다. 그런 부분에 대해서 @Query를 통해 JPQL을 작성해서 사용가능하지만 Querydsl의 장점을 고려해 이를 활용하고자 한다.

 

- 사용시 고려했던 사항은 DB내의 데이터 순환, 불필요한 데이터 제거, Spring JPA를 아예 사용하지 말자에 대한 여부다.

1. DB내의 데이터 순환 : 데이터를 하나 찾더라도 모든 데이터를 순환한다면 성능상 문제가 있을 것이다. 예를들어, 아이디에 해당하는 비밀번호를 찾더라도 종료가 되지 않으면 모든 회원정보를 순환하므로 문제가 된다. 따라서, 발견시 종료됨이 필요하다. 이를 위해, fetchFirst메서드를 사용했다 이는 limit(1) + selectOne()의 의미가 합쳐진 메서드로 발견시 바로 종료된다. fetch() 사용시 모든 데이터가 조회된다. 추가적으로 해당 API는 아이디는 기본키이고 하나만 존재하기 때문에 사용 가능하다.

2. 불필요한 데이터 제거 : 현재 엔터티를 전체 가져오고 있었다. 이를 비밀번호만 가져오게 수정하여 비밀번호가 비었다면 아이디가 없다고 판단했고 이 후, 비밀번호를 비교해 해당 로직을 처리했다.

3. Spring JPA 버리기 : 이를 고려를 많이 했던것 같다. 아예, 모든 로직을 querydsl로 바꾸고 Spring JPA 사용을 위한 모든 필요 비용을 삭제하고자 했다. 하지만 이는 많은 변경사항이 존재하기에 차차 설정하고자 한다.

// 기존
MemberEntity new_memberEntity = memberRepository.findByUserId(loginDTO.getUserId());
if (new_memberEntity == null) {
  throw new MessageException("아이디가 존재하지 않습니다.");
}
if(!passwordEncoder.matches(loginDTO.getUserPwd(), new_memberEntity.getUserPwd())){
  throw new MessageException("비밀번호가 일치하지 않습니다.");
}


// 변경
String user_pwd = queryFactory
        .select(memberEntity.userPwd)
        .from(memberEntity)
        .where(memberEntity.userId.eq(loginDTO.getUserId()))
        .fetchFirst();
if (user_pwd == null) {
  throw new MessageException("아이디가 존재하지 않습니다.");
}
if(!passwordEncoder.matches(loginDTO.getUserPwd(), user_pwd)){
  throw new MessageException("비밀번호가 일치하지 않습니다.");
}

 

위의 로직을 활용해, 비밀번호 찾기 API 등 특정 데이터만을 필요로 하는 API에 대해서 해당 로직을 적용시켰다.

 

또한 추가적으로 Entity보단 DTO를 활용한 Select가 효율적이다. 하지만 현재 예시와 같은 경우에는 조인 관계도 아니고, 칼럼하나만 가져오는 경우기에 굳이 사용하지 않았다. 만약 사용해야할 경우가 있다면 사용해볼 예정이다.

 

 

 

 

2. Query DSL을 통해 페이징 처리

 

음식점 List 출력 API

- 음식점 관련 데이터 중, 나라, 도시, 동, 음식점 타입을 입력 받아 해당 데이터에 맞는 음식점 리스트를 출력하되, 페이지 별로 6개씩 정하여 데이터를 출력하도록 설정한 API 이다.

변경 요소

1. 기존의 메서드명 또한 복잡하고 Pageable이란 객체를 매번 사용해 paging 처리를 하였는데 이를 offset메서드와 limit 메서드를 활용해 페이징 처리를 해주었다. 좀 더 가독성이 좋아졌다.

2. 또한, 기존에도 그렇게 처리했지만 정렬 관련해서 DB에서 처리할경우, filesort가 발생하는데 이는 많은 데이터가 아니라면 굉장히 비효율적인 정렬방식이라고 여겨진다. 해당 API는 6개의 데이터만 출력하면 되기에 stream을 통해 WAS내에서 정렬을 처리하게 설정하였다.

// 기존
Pageable pageable = PageRequest.of(page, size);
List<StoreEntity> storeEntityList =
    storeRepository.findByCountryAndCityAndDongAndType(country, city, dong, type, pageable);
return storeEntityList.stream().distinct().map(StoreEntity::getStoreName).sorted()
    .collect(Collectors.toList());




// 변경
List<String> store_name = queryFactory
    .select(storeEntity.storeName)
    .from(storeEntity)
    .where(storeEntity.country.eq(country)
        .and(storeEntity.city.eq(city))
        .and(storeEntity.dong.eq(dong))
        .and(storeEntity.type.eq(type)))
    .limit(size)
    .offset(page*size)
    .fetch();
return store_name.stream().distinct().sorted().collect(Collectors.toList());

 

 

 

 

 

 

 

 

'Trouble Shooting > Data API' 카테고리의 다른 글

QueryDSL - 3중 Join N+1 해결  (0) 2023.06.27
Query DSL, Entity대신 DTO로 받기  (0) 2023.06.08
SELECT + JOIN 개선에 대한 고찰  (0) 2023.05.15