티스토리 뷰

실무를 하다 보면 일괄로 데이터를 업데이트하거나 인서트하는 작업을 자주 하게 된다.

이런 작업을 기능으로 만들 경우에 입력된 값이 수정이나 입력될 수 있는 값인지 검증하는 작업이 들어간다.

간단하게 생각해보면 클라이언트에서 입력된 값이 a, b라면 아래와 같은 쿼리로 풀 수 있을 것이다.

select *
from Account
where Login_Id in ('a', 'b');

 

하지만, 복잡한 실무에서는 단일 값으로만 검증을 취할 수 없다.

예를 들어, 회원의 아이디 이메일, 아이디, 이름으로 검증을 취해야 할 경우 위와 같은 쿼리로는 불가능하게 된다.

물론 회원의 고유 일련번호(account_seq)로 판단은 가능하겠지만, 클라이언트에서 회원의 고유 일련번호는 모르는 경우가 많을 것이다.

 

이럴 경우, 여러 방법이 있겠지만 IN 절에 칼럼을 여러 개 지정하여 여러 파라미터를 넘길 수 있다.

select *
from Account
where (Email, Login_Id, Name) in
(('aaa@naver.com', 'aaa', '홍길동'),('bbb@naver.com', 'bbb', '테스트'));

해당 문법은 대표적인 대부분의 RDMS에서 지원하지만 MSSQL에서는 지원하지 않는 것으로 보인다.

 

여기서 한 단계 더 나아가, JPA를 사용할 경우 QueryDSL에서 어떻게 사용할 수 있는지 알아보자.

일단 IN 키워드를 기준으로 왼쪽 칼럼 부분은 QueryDSL의 Expressions.list로 표현할 수 있다.

Expressions.list(account.Email, account.loginId, account.name).in(...)

=> (Email, Login_Id, Name) in (...)

 

 IN 키워드의 우측 부분은 검색하고자 하는 파라미터 값이다.

이 값은 동적으로 몇 개의 값이 들어오는지 알 수 없기 때문에, 왼쪽 칼럼 부분과 달리 가공해주어야 한다.

여기서부터 삽질이 시작되었다.

QueryDSL SimpleExpression in메소드

in 메서드에 들어갈 수 있는 파리미터를 살펴보니 Expression을 가변 인자로 받고 있는 것을 볼 수 있다.

따라서 나는 처음에 검색할 List를 Expression 배열로 변환하여 사용하면 될 것이라 생각했다.

1. 첫 번째 삽질

@Override
public List<Account> findAllCustom(List<SearchAccount> searchAccounts) {

	return
    	query
            .selectFrom(account)
            .where(
            	Expressions.list(account.Email, account.loginId, account.name).in((Expression<? extends Tuple>[])searchAccounts.stream()
                	.map(entry -> Expressions.list(Expressions.constant(entry.getEmail()), Expressions.constant(entry.getLoginId()), Expressions.constant(entry.getName())))
                    .toArray(Expression[]::new))
            )
            .fetch();
}

 

각각의 칼럼에 검색 값으로 적용될 파라미터를 Expressions.list로 만들어주고, 이를 배열로 넘겼다.

이때, IN절에서 stream으로 구한 값을 배열로 바꾸고, (Expression<? extends Tuple>[]) 로 명시적으로 변환해주지 않는다면, 아래와 같이 타입을 명확히 할 수 없는 빌드 오류가 발생하기 때문에, 주의하자.

결과적으로는, 아쉽게 실패다.

쿼리 실행 결과

select account0_.Account_Seq as account_1_0_,
       account0_.Email       as email2_0_,
       account0_.Login_Id
                             as login_id3_0_,
       account0_.Name        as name4_0_,
       account0_.Use_Yn      as use_yn5_0_
from Account account0_
where (account0_.Email, account0_.Login_Id, account0_.Name) in
      ('aaa@test.co.kr', 'aaa', '홍길동', ('bbb@test.co.kr', 'bbb', '테스트'))

얼핏 성공한 것처럼 보이지만, IN 키워드 우측에 들어가는 검색 파라미터가 첫 번째 묶음 쪽에 괄호가 정상적으로 들어가지 않는 것이다.

 

여기서 여러 가지 시도를 해보았지만, 이 방법으로는 해결하지 못할 것 같아 다른 방법을 찾아 헤맸다.

어떻게 할 수 있을까 생각하다가 결국 QueryDSL gitbhub issue를 뒤지기 시작했다.

https://github.com/querydsl/querydsl/issues/2910

 

Multiple Column In clause Parameter · Issue #2910 · querydsl/querydsl

@Entity @Data class Image { private long id; private long imgId; private int index; } //In service, select all that its index and imgId in a map HashMap<integer, long=""> usedImageMap = ... List...</integer,>

github.com

위 글은 분명 나와 같은 질문에 답을 얻은 것으로 보이지만, 동작하지는 않는다.

 

2. 두 번째 삽질

github issue에서 다음과 같은 글을 찾았다.

https://github.com/querydsl/querydsl/issues/2920

 

Queryng With In Clause And Static Values · Issue #2920 · querydsl/querydsl

Observed vs. expected behavior Do the right query Steps to reproduce private List findRealizacaoAvaliacoes(final ResetAvaliacaoCommand cmd) { return baseRepository.findAll(qRealizacaoAv...

github.com

요약하면 다음과 같다.

QueryDSL에서 쿼리를 제너레이트 할 때, 우선순위에 따라 괄호가 매겨지는데 첫 번째 삽질과 같이 했을 경우에는 우선순위에 따라 첫 파라미터 묶음에 괄호가 생략되는 것이다.

이를 해결하기 위해서는 아래와 같이 template을 이용하라는 것이다.

    @Override
    public List<Account> findAllCustom(List<SearchAccount> searchAccounts) {

        return query
                .selectFrom(account)
                .where(
                        Expressions.list(account.Email, account.loginId, account.name).in(searchAccountIn(searchAccounts))
                )
                .fetch();
    }

    private Expression[] searchAccountIn(List<SearchAccount> searchAccounts) {

        List<Expression> tuples = new ArrayList<>();

        for(SearchAccount searchAccount : searchAccounts) {
            tuples.add(Expressions.template(Object.class, "({0}, {1}, {2})", searchAccount.getEmail(), searchAccount.getLoginId(), searchAccount.getName()));
        }

        return tuples.toArray(new Expression[0]);
    }

template을 이용하여 위와 같이 문자열로 지정해주면 정상적으로 쿼리가 나간다는 것이다.

위 쿼리의 테스트 결과로 정상적으로 원하는 쿼리가 나가는 것을 확인할 수 있었다.

 

3. 세 번째 삽질

하지만, 뭔가 찝찝하다. 

첫 번째 삽질의 경우 단건 검색 결과의 경우 정상적으로 나가지만, 멀티 검색 결과의 경우 괄호가 잘못 들어가는 경우였다.

그렇다면 두 번째 삽질에서의 코드를 단건으로 파라미터를 넘겨보면 어떻게 될까?

select account0_.Account_Seq as account_1_0_,
       account0_.Email       as email2_0_,
       account0_.Login_Id
                             as login_id3_0_,
       account0_.Name        as name4_0_,
       account0_.Use_Yn      as use_yn5_0_
from Account account0_
where (account0_.Email, account0_.Login_Id, account0_.Name) in ('aaa@test.co.kr', 'aaa', '홍길동')

분명 예상한 쿼리는 맞지만 쿼리 문법 오류가 나면서 실패한다. 왜일까?

지원하는 모든 RDMS를 살펴본 건 아니지만, 테스트한 MariaDB의 경우

단 건의 경우에도 무조건 부모 괄호가 하나 존재해야 한다.

select account0_.Account_Seq as account_1_0_,
       account0_.Email       as email2_0_,
       account0_.Login_Id
                             as login_id3_0_,
       account0_.Name        as name4_0_,
       account0_.Use_Yn      as use_yn5_0_
from Account account0_
where (account0_.Email, account0_.Login_Id, account0_.Name) in
      (('aaa@test.co.kr', 'aaa', '홍길동'))

 

결국 또 여기서 삽질을 하다가 어쨌든 쿼리를 template 문자열에 의해서 생성되는 것이니 템플릿에 괄호를 추가하면 되지 않을까 싶었다.

private Expression[] searchAccountIn(List<SearchAccount> searchAccounts) {

        List<Expression> tuples = new ArrayList<>();

        for(SearchAccount searchAccount : searchAccounts) {
            tuples.add(Expressions.template(Object.class, "(({0}, {1}, {2}))", searchAccount.getEmail(), searchAccount.getLoginId(), searchAccount.getName()));
        }

        return tuples.toArray(new Expression[0]);
    }

별다른 것 없이, "({0}, {1}, {2})" => "(({0}, {1}, {2}))"로 겉에 괄호를 하나 추가한 것으로 해결할 수 있었다.

 

더 효율적인 부분이 있을진 모르겠지만, 공식적으로 나와있는 사용법이나 글이 없는 것 같아 정리해보았다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함