query dsl - 1

김강현·2023년 5월 4일
0

query-dsl

목록 보기
2/3

Query DSL 기본 문법

query 위주의 실습이기에, 첫 데이터를 항상 넣고 시작.
(같은 class 내의 테스트 들만)
++ compileQuerydsl 실행해두기 (Q type 을 만들어줌)

JPQL 과 차이

    @Test
    public void startJPQL() {
        // find m1
        String queryString = "select m from Member m where m.username =:username";

        Member findByJPQL = em.createQuery(queryString, Member.class)
                .setParameter("username", "m1")
                .getSingleResult();

        assertThat(findByJPQL.getUsername()).isEqualTo("m1");
    }
    @Test
    public void startQuerydsl() {
        // find m1
        JPAQueryFactory queryFactory = new JPAQueryFactory(em);
//        QMember m = new QMember("member"); 이거는 에러가 뜨더라, Member 랑 같아서 그런가봄
        QMember m = new QMember("m");

        Member findMember = queryFactory.select(m).from(m).where(m.username.eq("m1")).fetchOne();
        assertThat(findMember.getUsername()).isEqualTo("m1");
    }

컴파일 시점에 오류를 발견할 수 있음!

이런식으로 지정해두고, 가져올 수 있다!! (할때마다 지정해주지 않아도 됨)

기본 Q-Type 활용

1. 직접 별칭 지정
QMember m = new QMember("m");

2. default 사용
QMember m = QMember.class; // <= new QMember("member1")

3. static 화 해서 바로 사용
QMember.member 말고 member 로 바로 사용.

JPQL 로 어떻게 생성되서 만들어지는지 확인하고 싶으면,

application.yml

jpa -> properties -> hibernate -> use_sql_comments:true 추가

검색 조건 Query

select, from 을 합쳐서 selectFrom 으로 쓸 수 있음 (같은 경우에)

Member findMember = queryFactory
    .selectFrom(member)
    .where(
        member.username.eq("m1").and(member.age.eq(10))
    ).fetchOne();
assertThat(findMember.getUsername()).isEqualTo("m1");

and 의 경우 이렇게 , 로 끊어 갈 수도 있다!

값으로 null 이 들어갈때는 해당 조건문을 무시한다. => 동적 쿼리 작성에 기가 막힌 구조!!!

결과 조회

주로 fetch 를 사용함. <- getResultList 와 비슷

        List<Member> fetch = queryFactory.selectFrom(member).fetch();

        Member fetchOne = queryFactory.selectFrom(member).fetchOne();

        // queryFactory.selectFrom(member).limit(1).fetchOne(); 이거랑 같음
        Member fetchFirst = queryFactory.selectFrom(member).fetchFirst();

        // paging 형식으로! 쿼리 2번 날림 (count)
        QueryResults<Member> results = queryFactory.selectFrom(member).fetchResults();
        results.getTotal(); // 전체 개수 -> count 쿼리를 따로 날림 => member 의 id 값으로만 count
        List<Member> content = results.getResults();
        // getLimit, getOffset 같은 여러 함수 지원

        // paging 말고 count 만 가지고 오고 싶을때
        long total = queryFactory.selectFrom(member).fetchCount();

fetch, fetchOne, fetchFirst, fetchResults, fetchCount 메소드로 결과 조회 가능!

정렬

이런식으로 바로 QMember 에서 엔티티 필드값에 desc, asc 적용 가능하고, 기타 설정들도 적용 가능하다!

페이징

offset 과 limit 로 가능하고, fetchResults 로 받아오면, 여러 페이징 관련 메소드를 사용할 수 있음!!

여기에 where 문 이나, 다른 테이블 참조 등 여러 복합쿼리가 나가게 되면, count 절에도 똑같이 적용이 되어 성능이 안나올 때가 있음! count 문을 위한 쿼리를 따로 짜주는게 좋음!!

집합


Tuple 은 querydsl 에서 제공해주는 자료형 (데이터 타입이 여러개 들어올때)

이런식으로 Tuple 값 접근 가능! => 실무에서는 DTO 로 처리함!

이런식의 groupBy 기능도 가능

join - 기본

        List<Member> result = queryFactory
                .selectFrom(member)
                .join(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();

        assertThat(result)
                .extracting("username")
                .containsExactly("m1", "m2");
  • teamQTeam.team 을 static 화 해놓은 것!

덧. 세타조인 이라고, 막 조인을 하는 것도 가능!

DB 가 최적화를 하겠지만, member 와 team Table 을 싹 불러와 NxM 테이블 만들고 where 절 처리! (cross join)

  • 단점 : 외부 조인이 불가능 함 => 조인 on 을 사용하여 해결 가능!

join - on 절

이를 qeurydsl 로 가져가면,

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

left join 을 할건데, team name 이 teamA 인 애들만 join 할거야.

나머지는 team 이 있어도 조회 안되는 걸루!!

추가) join 이라면.

List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .join(member.team, team).on(team.name.eq("teamA")).fetch();

join 을 할건데, team name 이 teamA 인 애들만 조인할거야.

on 절은 join에 조건을 걸어주는 것!!
-> inner 조인의 경우 on 이나 where 이나 같은 역할.
-> outer 조인의 경우 on 으로만 가능함!

join - fetch join

서브 쿼리

JPAExpressions 를 활용하여 서브쿼리 구현!
따로 QMember 별칭과 함께 지정해두고 사용해야함!!

// 나이가 가장 많은 회원들 조회
        QMember subMember = new QMember("subMember");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.eq(
                        JPAExpressions
                                .select(subMember.age.max())
                                .from(subMember)
                ))
                .fetch();

        for (Member member : result) {
            System.out.println("member = " + member);
        }
// 평균나이보다 많은 회원들 조회
        QMember subMember = new QMember("subMember");

        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.gt(
                        JPAExpressions
                                .select(subMember.age.avg())
                                .from(subMember)
                ))
                .fetch();

        for (Member member : result) {
            System.out.println("member = " + member);
        }

case 문

        List<String> result = queryFactory
                .select(member.age
                        .when(10).then("열살")
                        .when(20).then("스무살")
                        .otherwise("기타")
                )
                .from(member)
                .fetch();

        for (String s : result) {
            System.out.println("s = " + s);
        }

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

        for (String s : result) {
            System.out.println("s = " + s);
        }

가능하면 DB 에서 이런 작업들은 하지 않는 걸로!
이런 작업들은 DB 밖에서 하는 것이 왠만하면 좋음!! (클라이언트 나 presentation layer 에서)

상수 문자 더하기

        List<Tuple> result1 = queryFactory
                .select(member.username, Expressions.constant("A"))
                .from(member)
                .fetch();
        for (Tuple tuple : result1) {
            System.out.println("tuple = " + tuple);
        }

        List<String> result2 = queryFactory
                .select(member.username.concat(member.age.stringValue()))
                .from(member)
                .fetch();
        for (String v : result2) {
            System.out.println("v = " + v);
        }

.stringValue() 이게 은근 쓸 데가 많음 (enum 의 경우 자주 사용)

query dsl 중급 문법

프로젝션

Tuple 형태 말고 DTO 형태로!!

<MemberDto.java>

@Data
@NoArgsConstructor
public class MemberDto {
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

기존 JPQL 에서 dto 로 불러오는 법

List<MemberDto> resultList = em.createQuery(
    "select new study.querydsl.dto.MemberDto(m.username, m.age) "
    + "from Member m", MemberDto.class).getResultList();

QueryDSL 에서 더 깔끔하게 지원해줌

1. 프로퍼티 접근

2. 필드 직접 접근

3. 생성자 사용

4. 프로젝션과 결과 반환

@QueryProjection 어노테이션을 달아주면, Query DSL compile 할때 DTO 클래스가 Q형태로 새로 만들어진다!

정말 편하긴 한데!!
DTO는 보통 Respository 에서 Query DSL 함수로만 쓰이는 친구가 아님
Service, Controller 단에서도 쓰이게 되는데, Query DSL 관련 의존을 하고 있다는 건 뭔가 순수하지 않음...

동적쿼리

BooleanBuilder

    private List<Member> searchMember1(String usernameCond, Integer ageCond){

        BooleanBuilder builder = new BooleanBuilder();

        if(usernameCond != null){
            builder.and(member.username.eq(usernameCond));
        }

        if(ageCond != null){
            builder.and(member.age.eq(ageCond));
        }

        return queryFactory
                .selectFrom(member)
                .where(builder)
                .fetch();
    }

where 다중 파라미터

    private List<Member> searchMember2(String usernameCond, Integer ageCond){
        return queryFactory.selectFrom(member).where(
                usernameEq(usernameCond),
                ageEq(ageCond)
        ).fetch();
    }
    private Predicate usernameEq(String usernameCond){
        return usernameCond != null ? member.username.eq(usernameCond) : null;
    }

    private Predicate ageEq(Integer ageCond){
        return ageCond != null ? member.age.eq(ageCond) : null;
    }

수정, 삭제 배치 쿼리

  • 필드 업데이트
        long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(23))
                .execute();

        List<Member> result = queryFactory.selectFrom(member).fetch();

        for (Member member1 : result) {
            System.out.println("member1 = " + member1);
        }

update가 실행되고 나서 (벌크 연산), 영속성 컨텍스트가 유지되면, 조회를 하더라도 바뀐 값으로 업데이트 하지 않는 경우가 있다.
update 같은 벌크연산을 하고 난 뒤에는, em.flush 및 clear 하기를!!

  • 다른 벌크 기능
        long executeCount = queryFactory
                .update(member)
                //.set(member.age, member.age.add(1))
                .set(member.age, member.age.multiply(2))
                .execute();

        System.out.println("executeCount = " + executeCount);
        long executeCount = queryFactory
                .delete(member)
                .where(member.age.gt(18))
                .execute();
        System.out.println("executeCount = " + executeCount);

SQL function 호출

      List<String> result = queryFactory
                .select(Expressions.stringTemplate(
                        "function('replace', {0}, {1}, {2})",
                        member.username, "m", "member"))
                .from(member).fetch();

이런식으로 다양한 여러 function 을 사용할 수 있음!

Expressions.stringTemplate("function('lower', {0})", member.username)
member.username.lower() 와 같은 의미임. 대부분 다 querydsl 에 구현이 되어 있긴 함

profile
this too shall pass

0개의 댓글