동적 쿼리와 성능 최적화 조회
Builder 사용
조회 최적화용 DTO 추가
@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;
}
}
@QueryProjection을 추가했다.
QMemberTeamDto를 생성하기 위해 ./gradlew compileQuerydsl을 실행한다.
이 방식은 DTO가 Querydsl 라이브러리에 의존하게 된다는 단점이 있다.
싫다면 애너테이션을 삭제하고 아래 중 하나를 사용한다.
Projection.bean()
fields()
constructor()
회원 검색 조건
@Data
public class MemberSearchCondition {
// 회원명, 팀명, 나이(ageGoe, ageLoe)
private String username;
private String teamName;
private Integer ageGoe;
private Integer ageLoe;
}
동적 쿼리 구현
@Repository
public class MemberJpaRepository {
private final EntityManager em;
private final JPAQueryFactory queryFactory;
public List<MemberTeamDto> searchByBuilder(MemberSearchCondition condition) {
BooleanBuilder builder = new BooleanBuilder();
if (hasText(condition.getUsername())) {
builder.and(member.username.eq(condition.getUsername()));
}
if (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()));
}
return queryFactory
.select(new QMemberTeamDto(
member.id,
member.username,
member.age,
team.id,
team.name))
.from(member)
.leftJoin(member.team, team)
.where(builder)
.fetch();
}
}
테스트
@SpringBootTest
@Transactional
class MemberJpaRepositoryTest {
@Autowired
EntityManager em;
@Autowired
MemberJpaRepository memberJpaRepository;
@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.searchByBuilder(condition);
assertThat(result).extracting("username").containsExactly("member4");
}
}
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
MemberSearchCondition condition = new MemberSearchCondition();
select member1.id,
member1.username,
member1.age,
team.id,
team.name
from Member member1
left join
member1.team as team
condition에 조건이 아무것도 들어가지 않으면 모든 데이터를 끌고 온다.
운영에서는 데이터가 많기 때문에 기본 조건이 있거나 limit이라도 있어야 한다.
Where절에 파라미터 사용
public class MemberJpaRepository {
private final EntityManager em;
private final JPAQueryFactory queryFactory;
public List<MemberTeamDto> search(MemberSearchCondition condition) {
return queryFactory
.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();
}
private BooleanExpression usernameEq(String username) {
return hasText(username) ? null : member.username.eq(username);
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? null : team.name.eq(teamName);
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe == null ? null : member.age.goe(ageGoe);
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe == null ? null : member.age.loe(ageLoe);
}
}
Builder에 비해 가독성이 좋다.
메서드 재사용
@Repository
public class MemberJpaRepository {
private final EntityManager em;
private final JPAQueryFactory queryFactory;
// 메서드 재사용 가능
public List<Member> findMember(MemberSearchCondition condition) {
return queryFactory
.selectFrom(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()))
.fetch();
}
...
}
where 절에서 만들어놨던 메서드를 재사용할 수 있다.
Last updated