Spring Data

[Querydsl] 다이나믹 쿼리 사용하기

향로 (기억보단 기록을) 2019. 1. 25. 21:56
반응형

안녕하세요!
이번 시간에는 Querydsl에서의 다이나믹 쿼리를 어떻게 작성하면 좋을지에 대해 진행합니다.
처음 Querydsl을 쓰시는 분들이 가장 많이 실수하는 부분이니

그럼 시작합니다!

모든 코드는 Github에 있으니 참고하세요 :)

1. 문제 상황

예를 들어 상황에 따라 조건문이 생성 되어야 한다고 보겠습니다.

  • name이 오면 where name = name
  • address가 오면 where address = address
  • phoneNumber가 오면 where phoneNumber = phoneNumber
  • 2개 이상이 오면 모두 포함 where name = name and address = address and phoneNumber = phoneNumber

즉, 파리미터가 어떻게 오는지에 따라 where의 조건이 변경되는 것입니다.

이를 해결하기 위한 방법으로 BooleanBuilder를 사용하는 걸 자주 봅니다.

사용한 코드는 아래와 같습니다.

    @Override
    public List<Academy> findDynamicQuery(String name, String address, String phoneNumber) {

        BooleanBuilder builder = new BooleanBuilder();

        if (!StringUtils.isEmpty(name)) {
            builder.and(academy.name.eq(name));
        }
        if (!StringUtils.isEmpty(address)) {
            builder.and(academy.address.eq(address));
        }
        if (!StringUtils.isEmpty(phoneNumber)) {
            builder.and(academy.phoneNumber.eq(phoneNumber));
        }

        return queryFactory
                .selectFrom(academy)
                .where(builder)
                .fetch();
    }

예전 iBatis나 myBatis에서 사용하던것과 유사하죠?
if문으로 필요한 부분만을 BooleanBuilder에 추가하면서 쿼리를 만들었습니다.

이 방식의 문제점은 무엇일까요?
where문의 조건을 한눈에 보기 어렵습니다.
즉, 전혀 쿼리 형태를 예측하기가 어렵다는 것입니다.
지금은 단순한 쿼리이지만, 조금만 조건이 까다로워지면 추측하기도 힘든 쿼리가 될 것입니다.

2. 해결

Querydsl의 where에 조건문을 쓰되 파라미터가 비어있다면, 조건절에서 생략되길 바랍니다.
Querydsl에서는 이럴땔 대비해서 BooleanExpression을 준비했습니다.

BooleanExpression은 where에서 사용할 수 있는 값인데, 이 값은 ,and조건으로 사용합니다.
여기에 Querydsl의 wherenull이 파라미터로 올 경우 조건문에서 제외합니다.
2가지 속성을 이용해서 아래와 같이 코드를 작성할 수 있습니다.

@Override
    public List<Academy> findDynamicQueryAdvance(String name, String address, String phoneNumber) {
        return queryFactory
                .selectFrom(academy)
                .where(eqName(name),
                        eqAddress(address),
                        eqPhoneNumber(phoneNumber))
                .fetch();
    }

    private BooleanExpression eqName(String name) {
        if (StringUtils.isEmpty(name)) {
            return null;
        }
        return academy.name.eq(name);
    }

    private BooleanExpression eqAddress(String address) {
        if (StringUtils.isEmpty(address)) {
            return null;
        }
        return academy.address.eq(address);
    }

    private BooleanExpression eqPhoneNumber(String phoneNumber) {
        if (StringUtils.isEmpty(phoneNumber)) {
            return null;
        }
        return academy.phoneNumber.eq(phoneNumber);
    }

BooleanExpression을 리턴하는데, 각 메소드에서 조건이 맞지 않으면 null을 리턴합니다.
null을 리턴하니 where에서는 상황에 따라 조건문을 생성하게 됩니다.

첫번째 코드에 비해 굉장히 명확하게 쿼리가 예측 가능하죠?

실제로 잘 작동하는지 테스트 코드로 검증을 해보겠습니다.
첫번째로 단일 조건만 생성해봅니다.

@Test
    public void 동적쿼리_개선_name() {
        //given
        String targetName = "name";
        academyRepository.saveAll(Arrays.asList(
                new Academy(targetName, targetName, ""),
                new Academy("not target", "", "")
        ));

        //when
        List<Academy> academies = academyRepository.findDynamicQueryAdvance(targetName, "", "");

        //then
        assertThat(academies.size(), is(1));
        assertThat(academies.get(0).getAddress(), is(targetName));
    }

위 테스트 코드를 실행해보시면!

1

1개의 조건 (where academy.name=?) 만 적용되어 실행된 것을 확인할 수 있습니다.

만약 여러개의 조건이 들어온다면 어떻게 될까요!?
아래와 같이 테스트 코드를 하나더 추가해서 확인해봅니다.

    @Test
    public void 동적쿼리_개선_전체() {
        //given
        String targetName = "name";
        String targetAddress = "address";
        String targetPhoneNumber = "phoneNumber";
        academyRepository.saveAll(Arrays.asList(
                new Academy(targetName, targetAddress, targetPhoneNumber),
                new Academy("", "not target", "")
        ));

        //when
        List<Academy> academies = academyRepository.findDynamicQuery(targetName, targetAddress, targetPhoneNumber);

        //then
        assertThat(academies.size(), is(1));
        assertThat(academies.get(0).getAddress(), is(targetAddress));
    }

이 코드 역시 결과를 보면!

2

3개의 조건이 모두 적용되어 있는 것을 확인할 수 있습니다.


반응형