Super Kawaii Cute Cat Kaoani [Querydsl] 4. 중급 문법

[Querydsl] 4. 중급 문법

2024. 1. 27. 23:25
728x90
SMALL

📌 프로젝션과 결과 반환

✔ 프로젝션 : select 대상 지정

⚡ 프로젝션 대상이 하나

List<String> result = queryFactory
        .select(member.username)
        .from(member)
        .fetch();
  • 프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음
  • 프로젝션 대상이 둘 이상이면 튜플이나 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);

    }
}

 

✔ DTO 조회

➡ 순수 JPA에서 DTO 조회

MemberDto

package study.querydsl.dto;

import lombok.Data;

@Data
public class MemberDto {

    private String username;
    private int age;

    public MemberDto(){

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

 

순수 JPA에서 DTO 조회 코드

    @Test
    public void findDtoByJPQL(){
        List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age) from Member m", MemberDto.class)
                .getResultList();
        for (MemberDto memberDto : result) {
            System.out.println("memberDto = "+ memberDto);
        }
    }
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야함
  • DTO의 package이름을 다 적어줘야해서 지저분함
  • 생성자 방식만 지원함

 

Querydsl 빈 생성 (Bean population)

  • 결과를 DTO 반환할 때 사용
  • 다음 3가지 방법 지원
    • 프로퍼티 접근
    • 필드 직접 접근
    • 생성자 사용

✔ 프로퍼티 접근 - 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);
        }
    }

✔ 필드 직접 접근

    @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);
        }
    }

📎 별칭이 다를 때

package study.querydsl.dto;

import lombok.Data;

@Data
public class UserDto {
    private String name;
    private int age;

    public UserDto(){

    }
    public UserDto(String name, int age){
        this.name = name;
        this.age = age;
    }
}
    @Test
    public void findUserDto(){
        QMember memberSub = new QMember("memberSub");
        queryFactory
                .select(Projections.fields(
                        UserDto.class,
                        member.username.as("name"),
                        ExpressionUtils.as(
                                JPAExpressions
                                        .select(memberSub.age.max())
                                        .from(memberSub), "age"
                        )
                )).from(member)
                .fetch();
    }
  • 프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안
  • ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용
  • username.as("memberName") : 필드에 별칭 적용

 

✔ 생성자 사용

    @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);
        }
    }

 

⚡ @QueryProjection

생성자 + @QueryProjection

package study.querydsl.dto;

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

@Data
public class MemberDto {

    private String username;
    private int age;

    public MemberDto(){

    }
    @QueryProjection
    public MemberDto(String username, int age){
        this.username = username;
        this.age = age;
    }
}
  • ./gradlew compileQuerydsl
  • QMemberDto 생성 확인

 

@QueryProjection 활용

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

        for (MemberDto memberDto : result) {
            System.out.println("memberDto = " + memberDto);
        }
    }
  • 이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법
  • 다만 DTO에 QueryDSL 어노테이션을 유지 해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있음

distinct

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

 

cf) distinct는 JPQL의 distinct와 같다.

 

반응형

 

📌 동적 쿼리

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

  • BooleanBuilder
  • Where 다중 파라미터 사용BooleanBuilder 사용
    @Test
    public void 동적쿼리_BooleanBuilder() throws Exception{
        String usernameParam = "member1";
        Integer ageParam = 10;

        List<Member> result = searchMember1(usernameParam, ageParam);
        assertThat(result.size()).isEqualTo(1);
    }
    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 다중 파라미터 사용

 @Test
    public void 동적쿼리_WhereParam() throws Exception{
        String usernameParam = "member1";
        Integer ageParam = 10;

        List<Member> result = searchMember2(usernameParam, ageParam);
        assertThat(result.size()).isEqualTo(1);
    }
    private List<Member> searchMember2(String usernameCond, Integer ageCond){
        return queryFactory
                .selectFrom(member)
                .where(usernameEq(usernameCond), ageEq(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;
    }
  • where 조건에 null 값은 무시된다.
  • 메서드를 다른 쿼리에서도 재활용 할 수 있다.
  • 쿼리 자체의 가독성이 높아진다.

 

조합 가능

private BooleanExpression allEq(String usernameCond, Integer ageCond) {
    return usernameEq(usernameCond).and(ageEq(ageCond));
}
  • null 체크는 주의해서 처리해야함

 

 

 

📌 수정, 삭제 벌크 연산

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

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

기존 숫자에 1 더하기

long count = queryFactory
         .update(member)
         .set(member.age, member.age.add(1))
         .execute();
  • 곱하기: multiply(x)
     update member
     set age = age + 1
long count = queryFactory
        .delete(member)
     	.where(member.age.gt(18))
     	.execute();

 

주의❗) JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를 실행하 고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.

 

 

📌 SQL function 호출하기

  • SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.

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

String result = queryFactory
         .select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})",  member.username, "member", "M"))
         .from(member)
         .fetchFirst();
  • 소문자로 변경해서 비교해라
    .select(member.username)
    .from(member)
    .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})", member.username)))
.where(member.username.eq(member.username.lower()))

 

 

 

김영한님의 <실전! Querydsl>을 수강하고 작성한 글입니다

 

 

 


PREV

 

[Querydsl] 3. 기본 문법

📌 시작 - JPQL vs Querydsl 테스트 기본 코드 package study.querydsl; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.BeforeEach; i

nyeroni.tistory.com

NEXT

 

[Querydsl] 5. 실무 활용 - 순수 JPA와 Querydsl

📌 순수 JPA 리포지토리와 Querydsl ⚡ 순수 JPA 리포지토리 package study.querydsl.repository; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import org.springframework.stereotype.Repository; import s

nyeroni.tistory.com

 

728x90
LIST

BELATED ARTICLES

more