본문 바로가기
JAVA/Spring

[Spring] 검색 요청 JPQL에서 QueryDSL로 변경하기

by 알기 쉬운 코딩 사전 2024. 4. 15.
반응형

프로젝트를 진행 중 기존 검색 요청을 JQPL에서 QueryDSL로 변경하기로 하였다.

 

변경 이유에 대해서 간략하게 설명드리겠습니다.

 

아래에 변경 전 코드를 추가해 두었지만, JPA(JPQL)에서는 컴파일 시 N.P를 막지 못한다는 단점이 존재합니다. 그래서 필드의 Null처리를 위해서 서비스 코드의 코드 양 자체가 많아 진다는 단점이 존재합니다.

 

만약에 검색 조건이 2개에서 1개가 추가된다고 가정해 보면 필요한 JPQL의 최대 개수는 7개가 됩니다. 그렇게 되면 필요한 쿼리 메서드만 7개가 되어버리고 서비스 코드에 절대적으로 7줄이 추가될 수밖에 없습니다.

 

QueyrDSL을 사용하게 되면 기존의 SOLID를 위배하지 않는 선에서 서비스 코드의 코드 양 자체를 줄일 수 있게 됩니다.


변경 전 코드

SearchService.java

public List<SearchResumeResponse> findSearchResumes(SearchRequest request, Pageable pageable) {

    List<String> positions = request.positions();
    List<String> techStacks = request.techStacks();

    if (positions.isEmpty() && techStacks.isEmpty()) {
        return resumeRepository.findAll(pageable)
                .stream()
                .map(this::resumeToSearchResMap)
                .collect(Collectors.toList());
    } else if (positions.isEmpty() && !techStacks.isEmpty()) {
        // 포지션은 비어 있고 기술 스택은 값이 있는 경우 해당 기술 스택을 가진 이력서들을 검색
        return resumeRepository.findByTechStackIn(techStacks, pageable)
                .stream()
                .map(this::resumeToSearchResMap)
                .filter(resume -> new HashSet<>(resume.techStacks()).containsAll(techStacks))
                .collect(Collectors.toList());
    } else if (!positions.isEmpty() && techStacks.isEmpty()) {
        // 포지션은 값이 있고 기술 스택은 비어 있는 경우 해당 포지션을 가진 이력서들을 검색
        return resumeRepository.findByPositionIn(positions, pageable)
                .stream()
                .map(this::resumeToSearchResMap)
                .collect(Collectors.toList());
    } else {
//          AND 연산을 위한 필터링 조건
        return resumeRepository.findByPositionInAndTechStackIn(positions, techStacks, pageable)
                .stream()
                .map(this::resumeToSearchResMap)
                .filter(resume -> new HashSet<>(resume.techStacks()).containsAll(techStacks))
                .collect(Collectors.toList());
    }
}

 

변경 전에는 각각의 request 값의 조건으로 JPQL 문을 4가지를 만들어서 진행하였습니다.

 

구현해야 하는 기능이 많고 빠르게 기능 구현을 하는 것을 목적으로 두어서 이렇게 진행하였던 것입니다.

 

테스트를 위해서 다양한 test를 진행하였지만 진행 과정에서 List 형태의 데이터의 isBlank로의 값 처리 관리가 어려웠습니다.

-> null 인 경우 object.get(0).isBlank()에서 N.P가 발생한다. 이를 위해서는 조건문이 더욱 까다로워지며 변수들을 관리해 줘야 하는 코드가 들어가게 됩니다.

 

그래서 서비스 코드 자체의 복잡성이 있는 편입니다.


변경 후 코드

service.java

public List<SearchResumeResponse> findSearchResumes(SearchType searchType, SearchRequest request, Pageable pageable) {

    return resumeRepository.findSearchResume(searchType, request, pageable)
            .stream().map(this::resumeToSearchResMap)
            .collect(Collectors.toList());
}

 

 

서비스 코드가 확연하게 줄어든 것을 확인할 수 있습니다.

 

참고로 SearchType이라는 enum을 추가해 주었습니다.

 

Repository.java

public class ResumeRepositoryCustomImpl extends QuerydslRepositorySupport implements ResumeRepositoryCustom{

    public ResumeRepositoryCustomImpl() {
        super(Resume.class);
    }

    @Override
    public List<Resume> findSearchResume(SearchType searchType, SearchRequest request, Pageable pageable) {

        QResume resume = QResume.resume;

        BooleanBuilder builder = new BooleanBuilder();

        if (searchType == SearchType.POSITION){
            builder.and(resume.user.position.eq(request.position()));
        }

        if(searchType == SearchType.TECHSTACKS && !request.techStacks().isEmpty()) {
            for (String techStack : request.techStacks()) {
                builder.and(resume.user.techStack.contains(techStack));
            }
        }

        if (searchType == SearchType.POSITIONANDTECHSTACKS){
            for (String techStack : request.techStacks()) {
                builder.and(resume.user.techStack.contains(techStack));
            }
            builder.and(resume.user.position.eq(request.position()));
        }

        return from(resume)
                .where(builder)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .orderBy(resume.lastModifiedAt.desc())
                .fetch();
    }
}

 

Repository.interface

public interface ResumeRepositoryCustom {
    List<Resume> findSearchResume(SearchType searchType, SearchRequest searchRequest, Pageable pageable);
}

 

SearchType.enum

public enum SearchType {
    POSITION("포지션"),
    TECHSTACKS("기술 스택"),
    POSITIONANDTECHSTACKS("포지션과 기술 스택"),
    ALL("전체 검색");

    @Getter
    private final String description;

    SearchType(String description) {
        this.description = description;
    }
}

 

참고로 코드를 변경하면서 검색 조건을 명확하게 해주기 위해 enum class를 추가해 주었습니다.

 

궁금한 점이 있으시면 편하게 덧글 주세요! 최선을 다해 저의 지식을 공유해 드리겠습니다. 그 외에 피드백도 달게 받고 있습니다!

반응형

댓글