사용할만한 문법 정리

Chooooo·2024년 6월 24일
0

Querydsl

목록 보기
5/8

💪 Querydsl을 이용한 빈 생성

DTO를 반환하는 방법이 크게 3가지가 있다.

  • 프로퍼티로 접근하는 방식 (Setter 사용)
  • 필드 직접 접근
  • 생성자를 사용

💥 Projection과 결과 반환 - @QueryProjection

생성자 + @QueryProjection

프로젝션을 이용한 방법 중에 가장 깔끔한 방법일 수 있다.

@QueryProjection을 이용해 DTO도 Q타입의 클래스를 만들어서 이를 이용해 바로 만드는 방법이다.

Q타입의 클래스를 제공해주니 type-safe하다는 장점이 있다.

@QueryProjection을 이용해 생성하는 방법

@Test
void projectionWithJpa(){
    //given

    //when
    List<MemberDto> result = em.createQuery(
            "select new com.study.querydsl.dto.MemberDto(m.username, m.age)" +
                    "from Member m", MemberDto.class
            )
            .getResultList();
    //then
    for (MemberDto memberDto : result){
        System.out.println(memberDto.toString());
    }
}
@Test
void findDtoByQueryProjection(){
    //given

    //when
    List<MemberDto> result = queryFactory
            .select(new QMemberDto(member.username, member.age))
            .from(member)
            .fetch();
    //then
    for (MemberDto memberDto : result) {
        System.out.println(memberDto.toString());
    }
}
  • 다만 이 방식의 문제점은 Querydsl에 대한 의존성을 가지게 된다는 점(DTO에 QueryDSL 어노테이션을 유지해야 하는 점과 DTO까지 Q파일을 생성해야 하는 단점). 라이브러리를 바꾸게 되면 고쳐야할 DTO가 많아진다는 단점이 있다.

🌈 Best Practice - 동적 쿼리

실행시에 쿼리 문장이 만들어져 실행되는 쿼리문을 동적 쿼리라고 하는데, 동적으로 변수를 받아서 쿼리가 완성되는 걸 말한다.

  1. Predicate를 파라미터로 이용하는 방법
  2. BooleanBuilder를 이용하는 방법
  3. Predicate를 상속받은 BooleanExpression을 쓰는 방법 (Where 다중 파라미터 사용 )
    1. 이 방법이 더 코드가 깔끔하게 나온다. 실무에서 좀 더 사용하기에 좋다.

where 파라미터 이용 예제 (BooleanExpression)

@Test
void dynamicQueryUsingWhereParameter(){
    //given
    String usernameParam = "member1";
    Integer ageParam = 10;
    //when
    List<Member> result = searchMember2(usernameParam, ageParam);
    //then
    assertEquals(1, result.size());
}

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

private Predicate usernameEq(String usernameCond) {
    if(usernameCond == null) return null;

    return member.username.eq(usernameCond);
}

private Predicate ageEq(Integer ageCond) {
    if(ageCond == null) return null;

    return member.age.eq(ageCond);
}
  • usernameEq() 메서드가 null을 리턴하게 되면 Where()에 null값이 들어가게 되는데 이는 무시가 된다. 그러므로 동적 쿼리가 될 수 있다.
  • BooleanBuilder를 보는 것보다 Where 절에 적절한 메서드를 넣음으로써 가독성을 높일 수 있다. BooleanBuilder는 객체를 또 봐야 한다.

where 파라미터 조립 예제

@Test
void dynamicQueryUsingWhereParameter2(){
    //given
    String usernameParam = "member1";
    Integer ageParam = 10;
    //when
    List<Member> result = searchMember3(usernameParam, ageParam);
    //then
    assertEquals(1, result.size());
}

private List<Member> searchMember3(String usernameParam, Integer ageParam) {
    return queryFactory
            .selectFrom(member)
            .where(allEq(usernameParam, ageParam))
            .fetch();
}

private BooleanExpression allEq(String usernameParam, Integer ageParam) {
    return usernameEq1(usernameParam).and(ageEq(ageParam));
}

private BooleanExpression usernameEq1(String usernameCond) {
    if(usernameCond == null) return null;

    return member.username.eq(usernameCond);
}

private BooleanExpression ageEq1(Integer ageCond) {
    if(ageCond == null) return null;

    return member.age.eq(ageCond);
}
  • 조건 조립을 통해서 추상화를 적절히 할 수 있다는 장점과 재사용성이 높다는 장점이 있다.

수정 및 삭제 배치 쿼리

쿼리 한번으로 대량의 데이터를 수정하는 방식에 관한 것이다. 이를 벌크 연산이라고 한다.

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

벌크 연산 예제 - 수정

@Test
void bulkUpdate(){
    //given

    //when
    long count = queryFactory
            .update(member)
            .set(member.username, "비회원")
            .where(member.age.lt(28))
            .execute();
    //then
    assertEquals(2, count);
}
  • 벌크 연산은 조심해야 되는 게 있다. JPA에는 영속성 컨텍스트가 메모리에 올라와 있다. 하지만 벌크 연산은 DB에 바로 반영하는 것이기 때문에 영속성 컨텍스트의 상태와 DB의 상태가 달라지게 된다.
  • 즉, 벌크 연산을 한 후에 fetch()로 데이터를 조회하려고 해도 영속성 컨텍스트에 값이 있다면 변경된 값을 DB에서 가지고 와도 1차 캐시에 있는 값을 전달해준다.

벌크 수정 연산 후 데이터 가져오기 - 영속성 컨텍스트에서 가져오므로 반영이 안됨

@Test
@DisplayName("벌크 수정 연산 후 데이터 가져오기 - 영속성 컨택스트에서 가져오므로 반영이 안됨.")
void bulkUpdateAndFetch(){
    //given

    //when
    queryFactory
            .update(member)
            .set(member.username, "비회원")
            .where(member.age.lt(28))
            .execute();

    List<Member> result = queryFactory
            .selectFrom(member)
            .fetch();
    //then
    for (Member member : result) {
        System.out.println(member.getUsername() + " " + member.getAge());
    }
}
  • 변경된 값을 가지고 오기 위해서는 em.flush()와 em.clear()를 통해서 영속성 컨텍스트 값을 버리면 된다.

벌크 연산 - 모든 나이 + 1하기

@Test
void bulkAdd(){
    //given

    //when
    long count = queryFactory
            .update(member)
            .set(member.age, member.age.add(1))
            .execute();
    //then
    assertEquals(4, count);
}

벌크 연산 - 모든 나이 * 2하기

@Test
void bulkMultiply(){
    //given

    //when
    long count = queryFactory
            .update(member)
            .set(member.age, member.age.multiply(2))
            .execute();
    //then
    assertEquals(4, count);
}

벌크 연산 - 18세 이상 모든 회원 지우기

@Test
void bulkDelete(){
    //given

    //when
    long count = queryFactory
            .delete(member)
            .where(member.age.gt(18))
            .execute();
    //then
}
profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글