Querydsl - dto 반환, Projection, 동적 쿼리, Bulk Query, Sql function 호출

청포도봉봉이·2023년 3월 26일
0

Spring

목록 보기
8/35
post-thumbnail

JPQL

	@Test
    public void findDtoByJPQL() {
        List<MemberDto> list = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age)" +
                " from Member m", MemberDto.class).getResultList();

        for (MemberDto memberDto : list) {
            System.out.println("memberDto = " + memberDto);
        }
  • JPQL을 통한 구현 new 를 해서 dto의 생성자와 매핑한다.
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야함

Projection

Projections.bean

	@Test
    public void findDtoBySetter() {
        List<MemberDto> fetch = queryFactory
                .select(Projections.bean(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (MemberDto memberDto : fetch) {
            System.out.println("memberDto = " + memberDto);
        }
    }
  • Projection.bean을 통한 생성자를 생성한다.
  • Projections.bean은 결과 세트의 여러 컬럼을 자바 객체로 매핑하고자 할 때 사용된다.
  • 프로퍼티 직접 접근 - setter

Projections.fields

	@Test
    public void findDtoByField() {
        List<MemberDto> fetch = queryFactory
                .select(Projections.fields(MemberDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (MemberDto memberDto : fetch) {
            System.out.println("memberDto = " + memberDto);
        }
    }
  • Projections.field는 결과 세트의 단일 컬럼을 자바 객체의 필드 또는 변수에 직접 매핑하고자 할 때 사용된다.
  • 필드에 직접 접근

Projections.fields - 별칭이 다를때

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

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

        for (UserDto userDto : fetch) {
            System.out.println("userDto = " + userDto);
        }
    }
  • 별칭이 다를때 as, JPAExpressions를 사용해 필드명을 맞춰준다.

Projections.constructor

	@Test
    public void findDtoByConstructor() {
        List<UserDto> fetch = queryFactory
                .select(Projections.constructor(UserDto.class,
                        member.username,
                        member.age))
                .from(member)
                .fetch();
        for (UserDto userDto : fetch) {
            System.out.println("memberDto = " + userDto);
        }
    }
  • 생성자를 사용한 예시이다.
  • Projections.constructor는 결과 세트의 한 행(row)을 자바 클래스의 생성자를 사용하여 매핑할 때 사용된다. 즉, 결과 세트의 각 컬럼(column)은 해당 자바 클래스의 생성자 매개변수와 일치하도록 매핑된다.

생성자 + @QueryProjection

  package study.querydsl.dto;

  import com.querydsl.core.annotations.QueryProjection;
  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;```

      }
  }
  • @QueryProjection : Querydsl 프로젝션(projection)과 관련된 클래스의 생성자(Constructor)에 적용된다.
  • 어노테이션을 생성자에 추가하면, Querydsl은 해당 클래스를 사용하여 SQL 쿼리 결과를 매핑할 때 해당 생성자를 사용한다. 즉, SQL 결과 세트의 컬럼(column)이 클래스의 생성자 매개변수와 일치하도록 매핑된다.

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

동적 쿼리

BooleanBuilder

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

        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()
                .fetch();
    }
  • BooleanBuilder는 데이터베이스 쿼리를 동적으로 생성하기 위한 라이브러리이다. 일반적으로 SQL 쿼리를 작성할 때는 WHERE 조건절을 사용하여 특정 조건을 만족하는 데이터를 검색한다. 그러나 BooleanBuilder를 사용하면 여러 조건들을 조합하여 동적으로 WHERE 조건절을 생성할 수 있다.

  • BooleanBuilder를 사용하면 AND, OR, NOT과 같은 논리 연산자를 사용하여 여러 개의 조건을 조합할 수 있다. 또한, BooleanBuilder는 코드 가독성과 유지보수성을 높이는 데 도움이 되며, 복잡한 쿼리를 생성하기 위한 수고를 덜어준다.

  • BooleanBuilder는 다양한 데이터베이스와 호환되는 오픈 소스 라이브러리로 제공되며, Java와 Kotlin 등의 언어에서 사용할 수 있다.


다중 Where 사용

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

        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))
                .where(allEq(usernameCond, ageCond))
                .fetch();
    }

    private BooleanExpression usernameEq(String usernameCond) {
        //where에 null이 들어가면 무시가 된다.
        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, int ageCond) {
        return usernameEq(usernameCond).and(ageEq(ageCond));
    }
  • 메소드를 만들어 BooleanExpression 타입으로 반환한다면 where조건에 메소드와 매개변수만 넣어주면 된다는 장점이 있다.
  • BooleanExpression은 Querydsl 라이브러리에서 제공하는 인터페이스로, 데이터베이스 쿼리에서 WHERE 절에 해당하는 부분을 동적으로 생성하기 위해 사용된다.

Bulk query

    @Test
    @Commit
    public void bulkUpdate() {
        long count = queryFactory
                .update(member)
                .set(member.username, "비회원")
                .where(member.age.lt(28))
                .execute();

        //영속성 컨텍스트 초기화
        em.flush();
        em.clear();
        
        System.out.println("count = " + count);

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

       
        for (Member fetch1 : fetch) {
            System.out.println("fetch1 = " + fetch1);
        }
    }

    @Test
    public void bulkAdd() {
        long count = queryFactory
                .update(member)
//                .set(member.age, member.age.add(10))
//                .set(member.age, member.age.add(-10))
//                .set(member.age, member.age.multiply(10))
                .set(member.age, member.age.divide(10))
                .execute();

        System.out.println("count = " + count);

        em.flush();
        em.clear();

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

        for (Member fetch1 : fetch) {
            System.out.println("fetch1 = " + fetch1);
        }
    }

    @Test
    public void bulkDelete() {
        long count = queryFactory
                .delete(member)
                .where(member.age.gt(31))
                .execute();

        System.out.println("count = " + count);

        em.flush();
        em.clear();

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

        for (Member fetch1 : fetch) {
            System.out.println("fetch1 = " + fetch1);
        }
    }
  • bulk 쿼리는 영속성 컨텍스트 상태를 무시하고 바로 DB에 쿼리를 날려버린다.
  • 그래서 영속성 컨텍스트와 DB의 상태가 다를 수 있다.

  • 영속성 컨텍스트에서의 상태가 변하지 않았기 때문에 member1, mebmer2는 비회원으로 update된 결과로 조회되지 않는다.
  • 실제 DB에서는 비회원으로 update가 되어있는 상태임..
  • 우선권을 영속성 컨텍스트가 갖는다.
  • bulk 연산 후엔 영속성 컨텍스트를 초기화 해주자.
    • em.flush(); em.clear();

Sql function 호출

	@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 sqlFunctionToUpper() {
        List<Tuple> fetch = queryFactory
                .select(
//                        Expressions.stringTemplate("function('upper', {0})", member.username)
                        member.username.upper().as("name_upper"),
                        member.username.toUpperCase().as("name_toUpperCase")
                )
                .from(member)
                .fetch();

        for (Tuple tuple : fetch) {
            System.out.println("tuple = " + tuple);
        }
    }
}
  • SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.
profile
서버 백엔드 개발자

0개의 댓글