Querydsl - 중급 문법

Seongjin Jo·2022년 12월 29일
0

Querydsl

목록 보기
5/8

✔ 프로젝션 결과 반환


프로젝션은 : select 대상을 지정하는 것
프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음
프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회

1.프로젝션 대상이 하나 인 경우

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

2.두 개 이상인 경우 -> 반환타입이 Tuple이다.

        List<Tuple> result = queryFactory
                .select(member.username, member.age)
                .from(member)
                .fetch();

3.두 개 이상인 경우 -> DTO 조회 / jpql 방식

우선 MemberDto를 만든다

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


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

그 다음으로 디렉토리 파일 위치와 함께 MemberDto 객체를 생성한다

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

4.DTO 조회 / Querydsl 방식

3가지 방식이 있다.

  • 프로퍼티 접근 -setter 주입
  • 필드 직접 접근
  • 생성자 사용

1) 프로퍼티 접근 방식

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

2) 필드 접근 DTO - getter,setter 필요없음

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

2) 필드 접근 별칭이 다를 때

별칭이 다를 때

  • 필드로 접근 하기 때문에 필드 명이 맞아야 함.
  • .as("name")해주면 됨
  • 프로퍼티명과 필드접근방식 이름이 다를 때
  • ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용
  • 서브쿼리에서는 QMember의 이름이 겹치기 때문에 QMember 생성
QMember memberSub = new QMember("memberSub");
        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();

3) 생성자 사용

  • Dto 객체와 타입이 맞아야 됨.
    List<MemberDto> result = queryFactory
                  .select(Projections.constructor(MemberDto.class
                          ,member.username
                          ,member.age))
                  .from(member)
                  .fetch();

5.@QueryProjection

사용법

  • @QueryProjection를 MemberDto 생성자에 만들어준다.
  • 우측 상단에 Gradle 클릭
  • other -> compileQuerydsl 실행

장단점

  • constructor를 이용했을 때는 에러가 발생하면 런타임 즉, 에러가 발생했을때 오류는 잡는다

  • QMemberDto를 이용해서 하게되면 만들어진것 이외에 , 파라미터 타입이 잘못된값이 들어가면 빨간줄이 뜬다.

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

    List<MemberDto> result = queryFactory
                   .select(new QMemberDto(member.username, member.age))
                   .from(member)
                   .fetch();

✔ 동적 쿼리


1.BooleanBuilder

검색(조회)을 하는 경우 이름과 나이를 검색하는데, 이름이 있을 수도 있고 없을 수도 있고, 나이가 있을 수도 있고, 나이가 없을 수도 있고 이러한 방식으로 회원을 검색하는 쿼리

BooleanBuilder 라는 객체를 생성하고 builder에 이름과 나이를 조건에
따라 로직을 구성하고 .where() 조건 문에 넣어준다.

TestCode

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

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

searchMember1 메서드

private List<Member> searchMember1(String usernameParam, Integer ageParam) {
       //usernameParam값이 null인 경우,ageParam값이 null인 경우
       BooleanBuilder builder = new BooleanBuilder();
       if(usernameParam != null){
           builder.and(member.username.eq(usernameParam));
       }
       if(ageParam != null){
           builder.and(member.age.eq(ageParam));
       }
       return queryFactory
               .selectFrom(member)
               .where(builder)
               .fetch();
   }

2.Where 다중 파라미터 사용

원래 where문에 ','를 붙이면 and가 취급되는데 null값이 오면 무시해버리는 성질을 이용한 동적쿼리 해결 방법이다.

  • 메서드를 다른 쿼리에서도 재활용 할 수 있다.
  • 쿼리 자체의 가독성이 높아진다.
  • where 조건에 'null' 값은 무시된다

TestCode

@Test
    public void dynammicQuery_WhereParam(){
        String usernameParam = "member1";
        Integer ageParam = 10;

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

searchMember2 메서드

private List<Member> searchMember2(String usernameCond, Integer ageCond) {
        return queryFactory
                .selectFrom(member)
                .where(usernameEq(usernameCond),ageEq(ageCond))
                //.where(allEq(usernameCond,ageCond))
                .fetch();
    }

usernameEq,ageEq,allEq

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

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

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

✔ 벌크,수정 쿼리


수정,삭제 배치 쿼리 ( 별크 연산 )

  • 쿼리 한번으로 대량 데이터 수정
  • 변경감지로 인한 update 쿼리는 개별 엔티티 건 마다 쿼리가 나간다.
  • 한번에 한 쿼리로 처리하는 경우
    ex) 모든 개발자 연봉을 50%인상
    이런 경우는 한번에 날려서 트랜잭션하는게 좋다

1) 벌크 조회

public void bulkUpdate(){
        long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(28)) //lt 미만
                .execute();
        em.flush();
        em.clear();

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

벌크 연산은 항상 문제점이 존재한다. update쿼리는 db에 바로 정보를 인젝션 하기 때문에 영속성 컨텍스트와 db의 값이 다르게 된다. 이 상태에서 member의 정보를 출력하게 되면 db의 값과 영속성 컨텍스트의 값이 다르기 때문에 먼저 우선권을 갖는 영속성 컨텍스트의 member.username이 출력된다.그렇기 때문에 flush,clear를 해줘서 영속성 컨텍스트와 db의 정보가 동일하게 만들어주고 영속성 컨텍스트를 초기화한다. 그럼 문제 없다. JPQL은 트랜잭션이 끝날 때 마다 flush를 해주고 , Data Jpa는 @modifying(clearautomatically = true)라는 어노테이션으로 flush를 지원해준다. 하지만 querydsl에서는 그런 기능이 없기 때문에 직접 flush를 선언 해줘야한다.

2) 벌크 합

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

3) 벌크 수정

long count = queryFactory
                .delete(member)
                .where(member.age.gt(18))
                .execute();

✔ SQL function 호출하기


SQL function

  • SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.
  • MySQL은 yml파일에 설정해줘야한다.
    jpa:
         database-platform: org.hibernate.dialect.MySQL8Dialect
  • replace 함수는 다음과 같이 3개의 인자를 받으며,
  • {0}, {1}, {2} 각각
  • m.username, member, M 이다.
  • replace('문자열' or 열이름, '바꾸려는 문자열','바뀔 문자열')

member -> M

@Test
    public void sqlFunction(){
        List<String> result = queryFactory
                .select(
                        Expressions.stringTemplate(
                                "function('regexp_replace',{0},{1},{2})",
                                member.username, "member", "M"))
                .from(member)
                .fetch();
    }

소문자로 변경하기

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

0개의 댓글