우선 엔티티는 요청과 응답에서 성능을 최적화하고 엔티티를 엔티티만의 보존을 하기위해서 DTO를 제작해야한다. 다음의 코드는 member와 team을 조회하기윈한 dto이다.
- 회원의 id , 회원의 이름 , 회원의 나이 , 팀의 id , 팀의 이름을 조회해야한다고 가정해보자
- QuerryProjection을 사용하여 QType class를 만들 것이다.
package com.spring.jpadata.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Data;
@Data
public class MemberTeamDto {
private Long memberId;
private String username;
private int age;
private Long teamId;
private String teamName;
@QueryProjection
public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
this.memberId = memberId;
this.username = username;
this.age = age;
this.teamId = teamId;
this.teamName = teamName;
}
}
위와 같이 조회용 Dto도 @QueryProjection의 어노테이션을 사용해서 compileQueryDsl을 빌드하여 QType class 를 제작해주었다.
@Generated("com.querydsl.codegen.DefaultProjectionSerializer")
public class QMemberTeamDto extends ConstructorExpression<MemberTeamDto> {
private static final long serialVersionUID = 71897820L;
public QMemberTeamDto(com.querydsl.core.types.Expression<Long> memberId, com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age, com.querydsl.core.types.Expression<Long> teamId, com.querydsl.core.types.Expression<String> teamName) {
super(MemberTeamDto.class, new Class<?>[]{long.class, String.class, int.class, long.class, String.class}, memberId, username, age, teamId, teamName);
}
}
@Data
public class MemberSearchCondition {
//회원명 팀명 나이
private String username; // 회원의 이름
private String teamName; // 팀의 이름
private Integer ageGoe; // 회원의 나이 범위 (goe)
private Integer ageLoe; // 회원의 나이 범위 (loe)
}
그럼 이제 repository에 builder를 사용한 검색조건의 코드를 보겠다.
Null과 공백("")
아래의 코드에서의 팁이 하나있다. 이전의 블로그글에서 builder를 사용하여 검색조건을 만들 때 null의 조건을 사용하여 제작해야한다고 말했다. 하지만 실무에서나 프로젝트에서 주의할점은 String타입의 검색조건은 Null 뿐만 아니라 공백("") 도 잘 처리를 해주어야한다.
StringUtils.hasText()
StringUtils.hasText(값);을 사용하면 값이 있을 경우에는 true를 반환하고
공백이나 NULL이 들어올 경우에는 false를 반환하게 된다..as() : 알리아스 별칭 사용
앞서 만들어준 MemberTeamDto에는 member,team엔티티와 필드이름이 다른 필드가 존재했다. memberid, teamId, teamName은 각각의 엔티티완 필드의 이름이 불일치했다. 그래서 .as("별칭")을 사용해주어서 필드 이름의 불일치를 해결해준다.
//검색하기
public List<MemberTeamDto> searchByBulider(MemberSearchCondition condition) {
BooleanBuilder builder = new BooleanBuilder();
//null이 들어올 수 있고 ""이 들어올 수도 있다.
if (StringUtils.hasText(condition.getUsername())) {
builder.and(member.username.eq(condition.getUsername()));
}
if (StringUtils.hasText(condition.getTeamName())) {
builder.and(team.name.eq(condition.getTeamName()));
}
if (condition.getAgeGoe() != null) {
builder.and(member.age.goe(condition.getAgeGoe()));
}
if (condition.getAgeLoe() != null) {
builder.and(member.age.loe(condition.getAgeLoe()));
}
List<MemberTeamDto> memberTeamDtoList = queryFactory
.select(
new QMemberTeamDto(
member.id.as("memberId"),
member.username,
member.age,
team.id.as("teamId"),
team.name.as("teamName")))
.from(member)
.where(builder)
.leftJoin(member.team, team)
.fetch();
return memberTeamDtoList;
}
위의 코드를 설명하자면 BooleanBuilder 타입의 bulider를 사용하여 각각의 null조건 안에서 where() 문에 들어갈 조건을 더해준다. 또한 조회의 성능 최적화를 위해서 엔티티로 반환하는것이 아닌 @QueryProjection으로 생성된 QMemberTeamDto로 반환되게 해주었다. 이제 test를 진행하겠다.
@Test
public void searchTest() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
MemberSearchCondition condition = new MemberSearchCondition();
condition.setAgeGoe(35);
condition.setAgeLoe(40);
condition.setTeamName("teamB");
List<MemberTeamDto> result =
memberJpaRepository.searchByBulider(condition);
assertThat(result).extracting("username").containsExactly("member4");
}
위의 테스트코드는 제한나이의 범위와 팀의 이름을 검색 조건으로 주었다.
🎈주의 : 회원의 이름은 검색조건에 넣지 않았다(즉, null)인 상태이다. 그럼 다음과 같은 SQL이 실행된다.
select
member0_.member_id as col_0_0_,
member0_.username as col_1_0_,
member0_.age as col_2_0_,
team1_.team_id as col_3_0_,
team1_.name as col_4_0_
from
member member0_
left outer join
team team1_
on member0_.team_id=team1_.team_id
where
team1_.name=?
and member0_.age>=?
and member0_.age<=?
앞서 QueryDsl의 동적쿼리에 대한글에 중요한 키포인트 중 하나는 조건의 값이 null인 값은 자동으로 sql이 무시한다고 하였다. 위와 같이 member의 name은 검색 조건에 넣어주지 않아서 where 조건에 무시된 것을 볼 수있다.