스프링

 

중급자를 위해 준비한
[백엔드, 웹 개발] 강의입니다.

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!

✍️
이런 걸
배워요!

Querydsl을 기초부터 실무활용까지 한번에 배울 수 있습니다.

단순한 기능 설명을 넘어 실무활용 노하우를 배울 수 있습니다.

JPA를 사용할 때 동적 쿼리와 복잡한 쿼리 문제를 해결할 수 있습니다.

복잡한 쿼리, 동적 쿼리는 이제 안녕! 
Querydsl로 자바 백엔드 기술을 단단하게.

???? 본 강의는 로드맵 과정입니다.

  • 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. (링크)

 

 

강좌 

https://www.inflearn.com/course/querydsl-실전#

 

 

강의자료

https://github.com/braverokmc79/jpa-basic-lecture-file2

 

 

 

소스 :

https://github.com/braverokmc79/jpa-querydsl

 

 

 

 

 

 

 

 

 

 

[5] 중급 문법

 

 

22.프로젝션과 결과 반환 - 기본

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30136&tab=curriculum

 

프로젝션:

 

select 대상 지정 프로젝션 대상이 하나

   @Test
    public void simpleProjection(){
        List<String> result = queryFactory
                .select(member.username)
                .from(member)
                .fetch();

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


    }

 

쿼리출력

/* select
        member1.username 
    from
        Member member1 */ select
            member0_.username as col_0_0_ 
        from
            member member0_

 

 

프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음


프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회

 

 

튜플 조회

프로젝션 대상이 둘 이상일 때 사용

 

com.querydsl.core.Tuple

   @Test
    public void tupleProjection(){
        List<Tuple> result = queryFactory.
                select(member.username, member.age)
                .from(member)
                .fetch();

        for (Tuple tuple : result) {
            String username = tuple.get(member.username);
            Integer age = tuple.get(member.age);
            System.out.println("username = " + username);
            System.out.println("age = " + age);
        }

    }

 

쿼리출력

  /* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_

 

=>

username = member1
age = 10
username = member2
age = 20
username = member3
age = 30
username = member4
age = 40

 

 

 

 

 

 

 

 

 

 

 

 

★23.프로젝션과 결과 반환 - DTO 조회★

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30137&tab=curriculum

 

1) 순수 JPA에서 DTO 조회

 

MemberDto

package study.querydsl.dto;

import lombok.Data;

@Data
@NoArgsConstructor
public class MemberDto {

    private String username;
    private int age;

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

}

 

 

  @Test
    public void findDtoByJPQL(){
        List<MemberDto> resultList = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age) from Member m", MemberDto.class)
                .getResultList();
        for (MemberDto memberDto : resultList) {
            System.out.println("memberDto = " + memberDto);
        }
    }

 

쿼리 출력

/* select
        new study.querydsl.dto.MemberDto(m.username,
        m.age) 
    from
        Member m */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_

 

순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야함


DTO의 package이름을 다 적어줘야해서 지저분함


생성자 방식만 지원함

 

 

 

 

2) Querydsl 빈 생성(Bean population)

결과를 DTO 반환할 때 사용
다음 4가지 방법 지원

 

프로퍼티 접근


필드 직접 접근


생성자 사용

 

 

1. 프로퍼티 접근 - Setter

 

    @Test
    public void findDtoBySetter(){
        List<MemberDto> result = queryFactory
                .select(Projections.bean(MemberDto.class, member.username,
                        member.age))
                .from(member)
                .fetch();

        for (MemberDto memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }

    }

 

memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

 

 

2. 필드 직접 접근

    @Test
    public void findDtoByField(){
        List<MemberDto> result = queryFactory
                .select(Projections.fields(MemberDto.class, member.username,
                        member.age))
                .from(member)
                .fetch();

        for (MemberDto memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }

    }

 

 

3. 생성자 방식

   /** 
     * 생성자 방식
     * **/
    @Test
    public void findDtoByConstructor(){
        List<MemberDto> result = queryFactory
                .select(Projections.constructor(MemberDto.class, member.username,
                        member.age))
                .from(member)
                .fetch();

        for (MemberDto memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }

    }

 

 

 

 

4. 별칭이 다를 때

 

    @Test
    public void findUserDto(){
        QMember memberSub =new QMember("memberSub");

        List<UserDto> fetch = queryFactory
                .select(Projections.fields(UserDto.class,
                                member.username.as("name"),
                                ExpressionUtils.as(
                                        JPAExpressions
                                                .select(memberSub.age.max())
                                                .from(memberSub), "age")
                        )
                ).from(member)
                .fetch();

        for (UserDto userDto : fetch) {
            System.out.println("userDto = " + userDto);
        }
    }

 

프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안


ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용


username.as("memberName") : 필드에 별칭 적용

 

 

 

 

 

 

 

 

 

★★★24.프로젝션과 결과 반환 - @QueryProjection 

(대부분 이 방식을 사용한다 )

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30138&tab=curriculum

 

 

1) 생성자 + @QueryProjection

package study.querydsl.dto;

import com.querydsl.core.annotations.QueryProjection;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class MemberDto {

    private String username;
    private int age;

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

}

 

 

./gradlew compileQuerydsl


QMemberDto 생성 확인

 

 

 

2) @QueryProjection 활용

 

    @Test
    public void findDtoByQueryProjection(){
        List<MemberDto> fetch = queryFactory
                .select(new QMemberDto(member.username, member.age))
                .from(member)
                .fetch();

        for (MemberDto memberDto : fetch) {
            System.out.println("memberDto = " + memberDto);
        }
    }

 

 

이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다. 다만 DTO에 QueryDSL


어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다.

 

 

distinct

 

List<String> result = queryFactory
 .select(member.username).distinct()
 .from(member)
 .fetch();

 

 

참고: distinct는 JPQL의 distinct와 같다.

 

distinct 에 대해서는 다음 페이지를 참조

 

자바 ORM 표준 JPA 프로그래밍 - 10. 객체지향 쿼리 언어2 - 중급 문법 ★ 페치 조인(Fetch Join)

https://macaronics.net/index.php/m01/spring/view/2054

 

 

 

 

 

 

 

 

 

 

 

 

 

★★★25.동적 쿼리 - BooleanBuilder 사용

 

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30139&tab=curriculum

 

동적 쿼리를 해결하는 두가지 방식


1) BooleanBuilder


2 ) Where 다중 파라미터 사용

 

 

1) BooleanBuilder

    @Test
    public void dynamicQuery_BooleanBuilder(){
        String usernameParam ="member1";
        Integer ageParam =null;

        List<Member> result=searchMember1(usernameParam, ageParam);
        Assertions.assertThat(result.size()).isEqualTo(1);
    }

    private List<Member> searchMember1(String usernameCond, Integer ageCond) {
        /** username 이 필수값이아여야 할경우 다음과 같이 BooleanBuilder 생성자 설정
        BooleanBuilder builder =new BooleanBuilder(member.username.eq(usernameCond));
        */
         BooleanBuilder builder =new BooleanBuilder(member.username.eq(usernameCond));
        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();
    }

 

 

 

 

 

 

 

 

 

 

 

★★★★★

26.동적 쿼리 - Where 다중 파라미터 사용

 

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30140&tab=curriculum

 

코드가 깔끔하게 이해하기 가 쉽다.

 

    @Test
    public void dynamicQuery_WhereParam(){
        String usernameParam ="member1";
        Integer ageParam =null;

        List<Member> result=searchMember2(usernameParam, ageParam);
        Assertions.assertThat(result.size()).isEqualTo(1);
    }


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

       //** where 조건에서 null 이면 무시한다.
        return queryFactory
                .selectFrom(member)
               // .where(usernameEq(usernameCond), ageEq(ageCond))
                .where(allEq(usernameCond, ageCond))
                .fetch();
   }

    private BooleanExpression usernameEq(String usernameCond) {
       return  usernameCond!=null? member.username.eq(usernameCond) : null ;
    }

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


    /**
     *  다음과 같이 조합할 수 있다.
     */
    private BooleanExpression allEq(String usernameCond, Integer ageCond){
        return usernameEq(usernameCond).and(ageEq(ageCond));
    }

 

where 조건에 null 값은 무시된다.


메서드를 다른 쿼리에서도 재활용 할 수 있다.


쿼리 자체의 가독성이 높아진다.

 

 

 

조합 가능

 

private BooleanExpression allEq(String usernameCond, Integer ageCond) {
    return usernameEq(usernameCond).and(ageEq(ageCond));
}

 

 

null 체크는 주의해서 처리해야함

 

 

 

 

 

 

 

 

 

 

27.수정, 삭제 벌크 연산

 

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30141&tab=curriculum

 

쿼리 한번으로 대량 데이터 수정

    @Test
    @Commit
    public void bulkUpdate(){
        //member1 =10 -> DB member1
        //member2 =20 -> DB member2
        //member3 =30 -> DB member3
        //member4 =40 -> DB member4

        long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(28))
                .execute();

        em.flush();
        em.clear();
        //member1 =10 -> DB 비회원
        //member2 =20 -> DB 비회원
        //member3 =30 -> DB member3
        //member4 =40 -> DB member4

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

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

 

 

 

 

 /**
     * 벌크연산 빼기 - 로
     */
    @Test
    public void bulkMinus(){
        long count =queryFactory
                .update(member)
                .set(member.age, member.age.add(-1))
                .execute();
    }


    /**
     * 벌크 연산 곱하기
     */
    @Test
    public void bulkMultiple(){
        long count =queryFactory
                .update(member)
                .set(member.age, member.age.multiply(2))
                .execute();
    }


    /**
     * 벌크 연산 삭제
     */
    @Test
    public void bulkDelete(){
        queryFactory
                .delete(member)
                .where(member.age.gt(18))
                .execute();
    }


 

 

 

주의:

JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를


실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.

 

 

 

 

 

 

 

 

 

29.SQL function 호출하기

 

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30142&tab=curriculum

 

 

member M으로 변경하는 replace 함수 사용

    @Test
    public void sqlFunction(){
        List<String> result = queryFactory
                .select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})", member.username, "member", "M"))
                .from(member)
                .fetch();
        for (String s : result) {
            System.out.println("s = " + s);
        }
    }

 

 

소문자로 변경해서 비교해라

    @Test
    public void sqlFunction2(){
        List<String> result = queryFactory
            .select(member.username)
                .from(member)
                .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})",
                        member.username)))
                .fetch();

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

 

 

lower 같은 ansi 표준 함수들은 querydsl이 상당부분 내장하고 있다. 따라서 다음과 같이 처리해도 결과는 같다.

 

 

.where(member.username.eq(member.username.lower()))


 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

큰 물건을 가지고 있는 자는 소소한 물건은 물건이라 생각지도 않는다. 가령 억만장자가 5원이나 10원 같은 돈에 마음을 쓴다면 억만장자의 자격을 잃게 된다. -장자

댓글 ( 4)

댓글 남기기

작성