skarltjr / Memory_Write_Record

나의 모든 학습 기록
0 stars 0 forks source link

Query dsl #12

Open skarltjr opened 3 years ago

skarltjr commented 3 years ago

Query dsl vs JPQL


스프링데이터jpa의 확장성을 이용하여 query dsl을 사용하자

화면 캡처 2021-01-26 150044


public class StudyRepositoryExtensionImpl extends QuerydslRepositorySupport implements StudyRepositoryExtension {
    public StudyRepositoryExtensionImpl() {
        super(Study.class);
    }

    @Override
    public Page<Study> findByKeyword(String keyWord, Pageable pageable) {
        JPQLQuery<Study> query = from(study)
                .where(study.published.isTrue()
                        .and(study.title.containsIgnoreCase(keyWord))
                        .or(study.tags.any().title.containsIgnoreCase(keyWord))
                        .or(study.zones.any().localNameOfCity.containsIgnoreCase(keyWord)))
                .leftJoin(study.tags, tag).fetchJoin()
                .leftJoin(study.zones, zone).fetchJoin()
                .distinct();
        JPQLQuery<Study> studyJPQLQuery = getQuerydsl().applyPagination(pageable, query);
        QueryResults<Study> result = studyJPQLQuery.fetchResults();
        return new PageImpl<>(result.getResults(), pageable, result.getTotal());
    }
}

결과 조회

정렬

페이징

@Test
public void paging2() {
 QueryResults<Member> queryResults = queryFactory
 .selectFrom(member)
 .orderBy(member.username.desc())
 .offset(1)
 .limit(2)
 .fetchResults();
 assertThat(queryResults.getTotal()).isEqualTo(4);
 assertThat(queryResults.getLimit()).isEqualTo(2);
 assertThat(queryResults.getOffset()).isEqualTo(1);
 assertThat(queryResults.getResults().size()).isEqualTo(2);
}
  @Override
    public Page<Study> findByKeyword(String keyWord, Pageable pageable) {
        JPQLQuery<Study> query = from(study)
                .where(study.published.isTrue()
                        .and(study.title.containsIgnoreCase(keyWord))
                        .or(study.tags.any().title.containsIgnoreCase(keyWord))
                        .or(study.zones.any().localNameOfCity.containsIgnoreCase(keyWord)))
                .leftJoin(study.tags, tag).fetchJoin()
                .leftJoin(study.zones, zone).fetchJoin()
                .distinct();
        JPQLQuery<Study> studyJPQLQuery = getQuerydsl().applyPagination(pageable, query);
        QueryResults<Study> result = studyJPQLQuery.fetchResults();
        return new PageImpl<>(result.getResults(), pageable, result.getTotal());
    }

.select(member.count(), member.age.sum(), member.age.avg(), member.age.max(), member.age.min()) .from(member) .fetch();


- GroupBy 사용

팀의 이름과 각 팀의 평균연령을 구하자 @Test public void group() throws Exception { List result = queryFactory .select(team.name, member.age.avg()) .from(member) .join(member.team, team) .groupBy(team.name) .fetch(); Tuple teamA = result.get(0); Tuple teamB = result.get(1); assertThat(teamA.get(team.name)).isEqualTo("teamA"); assertThat(teamA.get(member.age.avg())).isEqualTo(15); assertThat(teamB.get(team.name)).isEqualTo("teamB"); assertThat(teamB.get(member.age.avg())).isEqualTo(35); }


#### join
- 기본조인 : 조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할
Q 타입을 지정하면 된다. 이 때 static import도 사용가능

@Test public void join() throws Exception { QMember member = QMember.member; QTeam team = QTeam.team; List result = queryFactory .selectFrom(member) .join(member.team, team) .where(team.name.eq("teamA")) .fetch(); assertThat(result) .extracting("username") .containsExactly("member1", "member2"); }

- join() , innerJoin() : 내부 조인(inner join)
- leftJoin() : left 외부 조인(left outer join)
- rightJoin() : rigth 외부 조인(rigth outer join)

#### join - on절  - 조인대상을 필터링하기
- 특정조건에 부합하는 대상을 조인하고 싶다.

List result = queryFactory .select(member, team) .from(member) .leftJoin(member.team, team).on(team.name.eq("teamA")) .fetch();

- 이 경우 select로 멤버와 teamA에 해당하는 팀을 고르기 때문에 여러가지 타입 = tuple로
- leftjoin인 만큼 멤버는 모두 조회하되 팀이 teamA에 해당하면 팀도 조인하여 가져온다. 당연히 해당x면 팀은 null

#### 최적화를 위한 fetch join
- n+1문제를 막기 위해 필요한 경우 fetch join을 통해 한 번에 끌어오자 

@Override public Page findByKeyword(String keyWord, Pageable pageable) { JPQLQuery query = from(study) .where(study.published.isTrue() .and(study.title.containsIgnoreCase(keyWord)) .or(study.tags.any().title.containsIgnoreCase(keyWord)) .or(study.zones.any().localNameOfCity.containsIgnoreCase(keyWord))) .leftJoin(study.tags, tag).fetchJoin() .leftJoin(study.zones, zone).fetchJoin() .distinct(); JPQLQuery studyJPQLQuery = getQuerydsl().applyPagination(pageable, query); QueryResults result = studyJPQLQuery.fetchResults(); return new PageImpl<>(result.getResults(), pageable, result.getTotal()); }

#### 서브쿼리 - JPAExpressions
- from절에서 서브쿼리

나이가 평균나이 이상인 회원을 고르자 @Test public void subQueryGoe() throws Exception { QMember memberSub = new QMember("memberSub"); List result = queryFactory .selectFrom(member) .where(member.age.goe( JPAExpressions .select(memberSub.age.avg()) .from(memberSub) )) .fetch(); assertThat(result).extracting("age") .containsExactly(30,40); }

- select 절에서 서브쿼리사용

List fetch = queryFactory .select(member.username, JPAExpressions .select(memberSub.age.avg()) .from(memberSub) ).from(member) .fetch(); for (Tuple tuple : fetch) { System.out.println("username = " + tuple.get(member.username)); System.out.println("age = " + tuple.get(JPAExpressions.select(memberSub.age.avg()) .from(memberSub))); }

- from 절의 서브쿼리 한계
- JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다. 당연히 Querydsl
도 지원하지 않는다. 하이버네이트 구현체를 사용하면 select 절의 서브쿼리는 지원한다. Querydsl도 하
이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.
- from 절의 서브쿼리 해결방안
1. 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.
3. nativeSQL을 사용한다.

- case문도 사용가능

List result = queryFactory .select(new CaseBuilder() .when(member.age.between(0, 20)).then("0~20살") .when(member.age.between(21, 30)).then("21~30살") .otherwise("기타")) .from(member) .fetch();

skarltjr commented 3 years ago

DTO로 반환하기 - 프로젝션

동적쿼리 ★

대량 수정, 벌크연산

skarltjr commented 3 years ago

동적쿼리와 페이징 그리고 dto로 반환하기

@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition,
Pageable pageable) {
 QueryResults<MemberTeamDto> results = queryFactory
 .select(new QMemberTeamDto(
 member.id.as("memberId"),
 member.username,
 member.age,
 team.id.as("teamId"),
 team.name.as("teamName")))
 .from(member)
 .leftJoin(member.team, team)
 .where(usernameEq(condition.getUsername()),
 teamNameEq(condition.getTeamName()),
 ageGoe(condition.getAgeGoe()),
 ageLoe(condition.getAgeLoe()))
 .offset(pageable.getOffset())
 .limit(pageable.getPageSize())
 .fetchResults();
 List<MemberTeamDto> content = results.getResults();
 long total = results.getTotal();
 return new PageImpl<>(content, pageable, total);
}
skarltjr commented 2 years ago

별도의 Pageable을 만들어보면 → Pageable pageable1 = PageRequest.of(1, 2.Sort.*by*(Sort.Direction.*DESC*,"createdAt"));