목차
순수 JPA 리포지토리와 Querydsl
•
순수 JPA 리포지토리와 Querydsl
•
동적쿼리 Builder 적용
•
동적쿼리 Where 적용
•
조회 API 컨트롤러 개발
순수 JPA 리포지토리와 Querydsl
순수 JPA& Querydsl 리포지토리
Code
순수 JPA& Querydsl 리포지토리 테스트
Code
JPAQueryFactory 스프링 빈 등록
다음과 같이 JPAQueryFactory를 스프링 븐으로 등록해서 주이받아 사용해도 된다.
package study.querydsl;
..
@SpringBootApplication
public class QuerydslApplication {
..
@Bean
JPAQueryFactory queryFactory (EntityManager em) {
return new JPAQueryFactory(em);
}
}
Java
복사
참고: 동시성 문제는 걱정하지 않아도 된다. 왜냐하면 여기서 스프링이 주입해주는 엔티티 매니저는 실제 동작 시점에 진짜 엔티티 매니저를 찾아주는 프록시용 가짜 엔티티 매니저이다. 이 가짜 엔티티 매니저는 실제 사용 시점에 트랜잭션 단위로 실제 엔티티 매니저(영속성 컨텍스트)를 할당해준다.
동적 쿼리와 성능 최적화 조회 - Builder 사용
MemberTeamDto - 조회 최적화용 DTO 추가
package study.querydsl.dto;
import com.querydsl.core.annotations.QueryProjection;
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;
}
}
Java
복사
•
@QueryProjections 을 추가했다. QMemberTeamDto를 생성하기 위해 ./gradlew compileQuerydsl을 한번 실행하자.
참고 : @QueryProjection을 사용하면 해당 DTO가 Querydsl을 의존하게 된다. 이런 의존이 싫으면, 해당 에노테이션을 제거하고, Projection.bean(), fields(), constructor()을 사용하면 된다.
회원 검색 조건
package study.querydsl.dto;
import lombok.Data;
@Data
public class MemberSearchCondition {
private String username;
private String teamName;
private Integer ageGoe;
private Integer ageLoe;
}
Java
복사
동적쿼리 - Builder 사용
builder를 사용한 예제
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(member.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();
}
Java
복사
동적 쿼리와 성능 최적화 조회 - Where절 파라미터 사용
Where절에 파라미터를 사용한 예제
public List<MemberTeamDto> searchByWhere(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()),
ageLog(condition.getAgeLoe())
).fetch();
}
private BooleanExpression usernameEq(String username) {
return hasText(username) ? member.username.eq(username) : null;
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName) : null;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe != null ? member.age.goe(ageGoe) : null;
}
private BooleanExpression ageLog(Integer ageLoe) {
return ageLoe != null ? member.age.loe(ageLoe) : null;
}
Java
복사
참고: where 절에 파라미터 방식을 사용하면 조건 재사용 가능
//where 파라미터 방식은 이런식으로 재사용이 가능하다.
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();
}
Java
복사
조회 API 컨트롤러 개발
편리한 데이터 확인을 위해 샘플 데이터를 추가하자.
샘플 데이터 추가가 테스트 케이스 실행에 영향을 주지 않도록 다음과 같이 프로파일을 설정하자.
프로파일 설정
src/main/resources/application.yml
spring:
profiles:
active: local
Java
복사
테스트는 기존 application.yml을 복사해서 다음 경로로 복사하고, 프로파일을 test로 수정하자
src/test/resources/application.yml
spring:
profiles:
active: test
Java
복사
이렇게 분리하면 main 소스콛와 테스트 소스 코드 실행시 프로파일을 분리할 수 있다.
샘플 데이터 추가
package study.querydsl;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import study.querydsl.entitiy.Member;
import study.querydsl.entitiy.Team;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Profile("local")
@Component
@RequiredArgsConstructor
public class InitMember {
private final InitMemberService initMemberService;
@PostConstruct
public void init() {
initMemberService.init();
}
@Component
static class InitMemberService {
@PersistenceContext
EntityManager em;
@Transactional
public void init() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
for (int i = 0; i < 100; i++) {
Team selectedTeam = i % 2 == 0 ? teamA : teamB;
em.persist(new Member("member" + i, i, selectedTeam));
}
}
}
}
Java
복사
조회 컨트롤러
package study.querydsl.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import study.querydsl.dto.MemberSearchCondition;
import study.querydsl.dto.MemberTeamDto;
import study.querydsl.repository.MemberJpaRepository;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberJpaRepository memberJpaRepository;
@GetMapping("/v1/members")
public List<MemberTeamDto> searchMemberV1(MemberSearchCondition condition) {
return memberJpaRepository.searchByWhere(condition);
}
}
Java
복사
•
예제 실행(psotman)