Spring Framework/[인프런] 실전! QueryDSL

Chapter 03. 중급 문법

계란💕 2023. 5. 1. 17:24

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

  • 프로젝션: SELECT 대상을 지정한다. 
  • 프로젝션 대상이 하나인 경우, 타입을 명확하게 지정 가능하다. 
  • 둘 이상인 경우는 튜플이나 DTO로 조회한다. 

 

 

  Ex) 프로젝션 결과 반환 - 기본

  • List<String> result = queryFactory.select(member.username). ....
  • 위와 같이 result의 타입을 username 형태인 String으로 정해줄 수 있다. 

 

 

  Ex) 프로젝션 결과 반환 - 튜플 조회

  • List<Tuple> result = queryFactory.select(member.username, member.age). ....

 


프로젝션과 결과 반환 - DTO

 

  Ex) 순수 JPA에서 DTO 조회

<hide/>
List<MemberDto> result = em.createQuery(
    "SELECT new study.querydsl.dto.MemberDto(m.username, m.age) FROM MEMBER m",
    MemberDto.class).getResultList();
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야한다. 
  • DTO의 패키지를 모두 적어야해서 코드가 복잡하다. 
  • 생성자 방식만 지원한다. 

 


Querydsl 빈 생성(bean population)

  • 결과를 DTO로 반환할 때 사용한다. 
  • 1) 프로퍼티에 접근한다. 
  • 2) 필드에 직접 접근한다. 
  • 3) 생성자를 사용한다. 

 

 

  Ex) 프로퍼티 접근 - setter

<hide/>
List<MemberDto> result = queryFactory.select(
            Projections.bean(MemberDto.class, member.username, member.age))
        .from(member)
        .fetch();
  • Projections.bean(): 프로젝션의 대상이되는 엔티티의 필드들을 하나의 클래스로 매핑해서 결괏값으로 반환하는 방식

 

 

  Ex) 필드에 직접 접근

<hide/>
List<MemberDto> result = queryFactory.select(Projections
        .fields(MemberDto.class, member.username, member.age))
    .from(member)
    .fetch();
  • Projections.fields(): 프로젝션 대상이 되는 엔티티의 필드 중에서 특정 필드들만 선택해서 결괏값으로 반환하는 방식이다. 

 

 

  Ex) 별칭이 다를 때

<hide/>
List<UserDto> result = 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"): 필드에 별칭을 사용한다. 

 

 

  Ex) 생성자 사용

<hide/>
List<MemberDto> result = queryFactory
        .select(Projections.constructor(MemberDto.class,
            member.username,
            member.age))
        .from(member)
        .fetch();
  • Projections.constructor()

 


프로젝션과 결과 반환 - @QueryProjection

 

 

  Ex) 생성자에  @QueryProjection 적용하기

<hide/>
@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;
    }
}
  • DTO클래스를 위와 같이 만들고 @QueryProjection 애너테이션을 붙인다. 
  • 터미널에 다음과 같이 입력해서 컴파일해주면 쿼리 관련된 파일이 새롭게 생성된다. 또는 gradle 탭에서 clean한 다음에 compile 클릭.
  • ./gradlew compileQuerydsl
  • 그러고나서 build 폴더를 확인해보면 다음과 같이 dto에 대한 클래스가 만들어진다. 

 

<hide/>
List<MemberDto> result = queryFactory
        .select(new QMemberDto(
            member.username,
            member.age))
        .from(member)
        .fetch();
  • 이 방식은 컴파일러로 타입을 체크할 수 있으므로 안전한 방법이다. 
    • 그러나 DTO에@QueryProjection을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야하는 단점이 있다. 

 

<hide/>
List<String> result0 = queryFactory
    .select(member.username).distinct()
    .from(member)
    .fetch();
  • distinct는 JPQL의 distinct와 같다. 

 


동적 쿼리 해결 방법

  • 동적 쿼리: 프로그램 실행 중에 조건에 따라 쿼리문이 동적으로 생성되는 쿼리를 말한다. 실행 시점에 쿼리문이 생성되며 쿼리문에 사용되는 조건문, 테이블명, 칼럼명 등이 로직에 따라 달라질 수 있는 쿼리를 말한다. 
  • 예를 들어, 검색 조건이 동적으로 결정되는 검색 기능을 구현할 때, 사용자가 입력한 검색어에 따라 쿼리문이 동적으로 생성되어야한다. 이 때 동적 쿼리를 사용하면 적절한 쿼리문을 생성해서 검색 기능을 구현할 수 있다. 

 

 

  Ex 1) BooleanBuilder 

<hide/>
// 동적 쿼리 - 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();
}

@Test
public void dynamicQuery_booleanBuilder() throws Exception {
    String usernameParam = "member1";
    Integer ageParam = 10;
    List<Member> result = searchMember1(usernameParam, ageParam);
    Assertions.assertThat(result.size()).isEqualTo(1);
}

 

 

 

  Ex 2) WHERE 다중 파라미터 사용

<hide/>
// 동적 쿼리 - where 다중 파라미터
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 List<Member> searchMember2(String usernameCond, Integer ageCond) {
    return queryFactory.selectFrom(member)
        .where(usernameEq(usernameCond), ageEq(ageCond))
        .fetch();
}
@Test
public void dynamicQuery_whereParam() throws Exception {
    String usernameParam = "member1";
    Integer ageParam = 10;
    List<Member> result = searchMember2(usernameParam, ageParam);
    Assertions.assertThat(result.size()).isEqualTo(1);
}
  • where 절 안에 매개변수로 여러 개 넣어줄 수 있도록 여러 개의 "eq"  메서드를 만든다. 
  •  search() 메서드는 실질적인 쿼리 역할을 한다. 
  • where 절 안에 null 값이 있으면 무시된다. 
  • 메서드를 다른 쿼리에서도 재활용할 수 있기 때문에 효율적이다. 
  • 가독성이 올라간다. 
  • cf) and()를 이용해서 ageEq().and.usernameEq() 와 같은 형태로 조합도 가능하다. 

수정, 삭제 벌크 연산

 

  Ex) 문자열, 숫자, 대량 삭제

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

long count1 = queryFactory.update(member)
    .set(member.age, member.age.add(1))
    .execute();

long count2 = queryFactory.delete(member)
    .where(member.age.gt(18))
    .execute();
  • update().set(원래 필드, 바뀐 값)
  • delete() 해주면 조건에 맞는 데이터를 대량으로 삭제할 수 있다. 

 


SQL function 호출하기

  • SQL function은 SUM(), MAX(), COUNT()와 같이 데이터베이스 제공하는 기본 함수 또는 사용자 정의 함수를 말하며  데이터를 조작하거나 계산하는데 사용되는 함수를 말한다. 
  • SQL_function은 JPA와 같이 Dialect에 등록된 내용만 호출 가능하다. 
  • member => 'M' 으로 변경하는 replace 함수를 사용한다. 

 

 

  Ex) member => M으로 변경하는 replace 함수 사용

<hide/>
String result = queryFactory.select(Expressions.stringTemplate("function('replace', {0}, {1}, {2})", member.username, "member", "M"))
        .from(member)
        .fetchFirst();

 

 

  Ex) 소문자로 바꿔서 비교하기 

<hide/>
String result = queryFactory.select(member.username)
        .from(member)
            .where(member.username
                .eq(Expressions.stringTemplate("function('lower', {0})", member.username))).fetchFirst();
  • lower() 같은 ansi(Amerinal National Standards Institute, 미국 국립 표준 협회) 표준 함수들은 querydsl에서 기본적으로 내장하고 있다. 

 


출처 -  https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84