과거 우리가 where절은 ,로 구분하여, 여러 표현식을 인자로 주면 그게 and로 묶인다고 했다.
https://qwefdg3.tistory.com/937
그래서, 표현식을 만들어 where절 안에 인자로 주면, 그게 알아서 여러 개면 and로 묶여 쿼리를 만들어 준다.
그렇기에,
private BooleanExpression usernameEq(String username){
return StringUtils.hasText(username) ? member.username.eq(username) : null;
}
private BooleanExpression teamNameEq(String teamName){
return StringUtils.hasText(teamName) ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe){
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe){
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
이렇게 조건에 따라 표현식을 반환하는 메소드를 만들어 두고,
public List<MemberTeamDto> search(MemberSearchCondition condition){
return query.select(new QMemberTeamDto(
member.id, member.username, member.age, team.id, team.name
)).from(member).leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
)
.fetch();
}
호출해서 쓴다.
그러면 알아서 저 표현식(조건식인 표현식)들은 where 안에서 and로 묶인다.
@Test
public void search(){
MemberSearchCondition condition = new MemberSearchCondition();
condition.setAgeGoe(20);
condition.setAgeLoe(40);
condition.setTeamName("teamB");
List<MemberTeamDto> result = memberJpaRepository.search(condition);
}
테스트 해 보면,
/* select
member1.id,
member1.username,
member1.age,
team.id,
team.name
from
Member member1
left join
member1.team as team
where
team.name = ?1
and member1.age >= ?2
and member1.age <= ?3 */ select
m1_0.member_id,
m1_0.username,
m1_0.age,
m1_0.team_id,
t1_0.name
from
member m1_0
left join
team t1_0
on t1_0.team_id=m1_0.team_id
where
t1_0.name=?
and m1_0.age>=?
and m1_0.age<=?
의도했던 대로 쿼리가 잘 나간다.
그런데, 아무래도
public List<MemberTeamDto> search(MemberSearchCondition condition){
return query.select(new QMemberTeamDto(
member.id, member.username, member.age, team.id, team.name
)).from(member).leftJoin(member.team, team)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
)
.fetch();
}
이렇게 where절에 더덕더덕 붙여놓는게 좋아보이지 않는다. 저걸 따로 빼고 싶다.
그런데, 이러면은 기존에
private BooleanExpression usernameEq(String username){
return StringUtils.hasText(username) ? member.username.eq(username) : null;
}
private BooleanExpression teamNameEq(String teamName){
return StringUtils.hasText(teamName) ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe){
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLoe(Integer ageLoe){
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
이렇게 되는데, 저것들은 한번에 모아서
private BooleanExpression allEq(MemberSearchCondition condition){
return usernameEq(condition.getUsername())
.and(teamNameEq(condition.getTeamName()))
.and(ageGoe(condition.getAgeGoe()))
.and(ageLoe(condition.getAgeLoe()));
}
이걸 만들으려 해도, usernameEq에서 null이 반환되면 null.and가 불가능 하다.
그래서 해결할 수 있는 방향 하나가,
private BooleanExpression usernameEq(String username){
return StringUtils.hasText(username) ? member.username.eq(username) : Expressions.TRUE;
}
private BooleanExpression teamNameEq(String teamName){
return StringUtils.hasText(teamName) ? team.name.eq(teamName) : Expressions.TRUE;
}
private BooleanExpression ageGoe(Integer ageGoe){
return ageGoe != null ? member.age.goe(ageGoe) : Expressions.TRUE;
}
private BooleanExpression ageLoe(Integer ageLoe){
return ageLoe != null ? member.age.loe(ageLoe) : Expressions.TRUE;
}
이렇게 null 대신 true 표현식을 반환하는 거다.
빈 표현식 객체를 반환하려고 했는데, 없는 것 같다.
여튼 저렇게 해서
@Test
public void search(){
MemberSearchCondition condition = new MemberSearchCondition();
condition.setAgeGoe(20);
condition.setAgeLoe(40);
condition.setTeamName("teamB");
List<MemberTeamDto> result = memberJpaRepository.searchEq(condition);
}
돌려 보면,
/* select
member1.id,
member1.username,
member1.age,
team.id,
team.name
from
Member member1
left join
member1.team as team
where
true
and team.name = ?1
and member1.age >= ?2
and member1.age <= ?3 */ select
m1_0.member_id,
m1_0.username,
m1_0.age,
m1_0.team_id,
t1_0.name
from
member m1_0
left join
team t1_0
on t1_0.team_id=m1_0.team_id
where
true
and t1_0.name=?
and m1_0.age>=?
and m1_0.age<=?
쿼리는 잘 나간다.
그런데 문제가 저 true.
물론 기능상으로는 아무 문제가 없다.
심지어 저렇게 쿼리가 있어도,
예를 들어
@Test
public void search(){
MemberSearchCondition condition = new MemberSearchCondition();
condition.setTeamName("teamB");
List<MemberTeamDto> result = memberJpaRepository.searchEq(condition);
}
/* select
member1.id,
member1.username,
member1.age,
team.id,
team.name
from
Member member1
left join
member1.team as team
where
true
and team.name = ?1
and true
and true */ select
m1_0.member_id,
m1_0.username,
m1_0.age,
m1_0.team_id,
t1_0.name
from
member m1_0
left join
team t1_0
on t1_0.team_id=m1_0.team_id
where
true
and t1_0.name=?
and true
and true
이렇게 되어 있어도
저 and true 저러 한 부분들은 다 알아서 DB내에서 최적화가 된다. 또 만약 or조건으로 하고 싶어도
즉, 기능상으로는 문제가 없고, 최적화 상으로도 문제가 없다.
그런데, 아무래도 저렇게 의미없는 쿼리가 나간다는게 유지보수상에서 조금 그렇긴 하다.
내 생각에 이거, 괜찮아 보인다.
만약 and true 이런 거 보고 아, 이거 동적쿼리 조건이구나? 하고 생각할 수 있는 사람끼리 보면 괜찮을 것 같다.
또 위에 조건 표현식을 삼항연산자로 바로 반환했듯이, 저런 부분은 코드 가독성 부분에서 상당히 괜찮다. (단, 용도는 써줘야 할 듯 싶다.)
하지만, 저렇게 의미없는 쿼리가 뭔지 한번 찾아봐야 한다는 점이 조금 그렇다.
+++++
https://www.inflearn.com/questions/1091244/alleq-만들기
선생님이 좋은 방식이 아니라고 한다. 아무래도 의미없는 쿼리가 나가는 것의 모호성을 더 하자로 여기신 것 같다.
https://www.inflearn.com/questions/94056/강사님-where-다중-파라미터를-이용한-동적-쿼리-사용에-대한-질문입니다
그냥 BooleanBuilder로 반환하면 된다!
private BooleanBuilder ageEq(Integer age) {
return nullSafeBuilder(() -> member.age.eq(age));
}
private BooleanBuilder roleEq(String roleName) {
return nullSafeBuilder(() -> member.roleName.eq(roleName));
}
public static BooleanBuilder nullSafeBuilder(Supplier<BooleanExpression> f) {
try {
return new BooleanBuilder(f.get());
} catch (IllegalArgumentException e) {
return new BooleanBuilder();
}
}
잘 모르겠지만, 공급기는 보통 값을 공급하는 역할을 하는 콜백으로 넘기는 데,
여기서 IllegalArgumentException 즉, 잘못된 인자 예외가 터지는 이유는,
eq()에 null이 들어가기 때문에 그렇다. 그렇다면 빈 builder를 줘버린다.
애초에 where가 builder 처리도 가능하다.
+++
근데 이거, eq는 되는데 like 같은 경우는 안된다.
eq만 되는 이유는,
public BooleanExpression eq(T right) {
if (right == null) {
throw new IllegalArgumentException("eq(null) is not allowed. Use isNull() instead");
} else {
return eq(ConstantImpl.create(right));
}
}
eq는 이렇게 내부적으로 알아서 null 처리를 해준다.
그래서 저 IllegalArgumentException을 받아서 처리 한 거고.
근데, like나 contain같은 경우는..
public BooleanExpression contains(String str) {
return contains(ConstantImpl.create(str));
}
그냥 호출해 버린다.
그래서 따로 null 처리를 해야 한다.
'스프링데이터 + JPA > QueryDSL' 카테고리의 다른 글
31. 순수 JPA -> 스프링 데이터 JPA (0) | 2023.12.04 |
---|---|
30. 조회 API 컨트롤러 만들기 (0) | 2023.12.03 |
28. 동적 쿼리와 성능 최적화 조회, Builder (0) | 2023.12.03 |
27. 순수 JPA와 QueryDSL (0) | 2023.12.03 |
26. SQL 함수 호출 (0) | 2023.12.02 |