티스토리 뷰
실무를 하다 보면 일괄로 데이터를 업데이트하거나 인서트하는 작업을 자주 하게 된다.
이런 작업을 기능으로 만들 경우에 입력된 값이 수정이나 입력될 수 있는 값인지 검증하는 작업이 들어간다.
간단하게 생각해보면 클라이언트에서 입력된 값이 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 키워드의 우측 부분은 검색하고자 하는 파라미터 값이다.
이 값은 동적으로 몇 개의 값이 들어오는지 알 수 없기 때문에, 왼쪽 칼럼 부분과 달리 가공해주어야 한다.
여기서부터 삽질이 시작되었다.
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}))"로 겉에 괄호를 하나 추가한 것으로 해결할 수 있었다.
더 효율적인 부분이 있을진 모르겠지만, 공식적으로 나와있는 사용법이나 글이 없는 것 같아 정리해보았다.
'Web > JPA & QueryDSL' 카테고리의 다른 글
[JPA] 각각 다른 패키지에 클래스 이름이 같은 엔티티가 있을 경우 인식 문제 (30) | 2022.01.02 |
---|---|
[QueryDSL & JPQL] Converter가 작동하지 않는 경우 (32) | 2021.09.20 |
[QueryDSL] from, join 절에 서브쿼리 사용하기 (0) | 2021.09.11 |
- Total
- Today
- Yesterday
- @subselect
- IN Clause
- 자바
- 네트워크
- @subquery
- 특징
- list
- playsinline
- Queue
- jwplayer
- 의미
- SET
- playbackRate
- oauth
- @EventListener
- 원리
- QueryDSL
- login
- 장점
- SDK
- Animation
- beforeunload
- 예제
- join subquery
- Multi IN Clause
- 관리자 도구
- API
- 로그인
- on('seek')
- map
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |