RE-Heat 개발자 일지

[Querydsl] [4] 중급 문법 (하편) - 동적 쿼리·벌크 연산·SQL function 본문

백엔드/Querydsl

[Querydsl] [4] 중급 문법 (하편) - 동적 쿼리·벌크 연산·SQL function

RE-Heat 2023. 9. 30. 00:28
 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자바 백엔

www.inflearn.com

인프런 김영한 님의 강의를 듣고 작성한 글입니다.

 

■ 순수 JPA에서 동적 쿼리 사용 방법

1. 문자열 조합 : 오류 찾기 힘듦.

2. JPA Criteria : 어떤 JPQL이 실행될지 예상하기 어려움

 

■ Querydsl 동적쿼리 사용 방법

1. BooleanBuilder

2. Where 다중 파라미터 사용

 

[4] 동적 쿼리 - BooleanBuilder 사용

@Test
public void dynamicQuery_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();
}
  • if문으로 조건이 null값이 아니면 BooleanBuilder의 builder.and() 메서드를 통해 조건을 넣어준다.

 

  • username과 age가 and 조건으로 들어간 것을 확인할 수 있다.(좌)
  • 만일 ageParam에 null 값이 들어가면 SQL에도 age가 빠진다.(우)

 

■ 값을 필수로 넣고 싶으면?

BooleanBuilder 객체를 생성할 때 Null값이 오면 안되는 값을 미리 넣어준다.


[5] 동적 쿼리 - Where 다중 파라미터 사용

영한님이 실무에서 애용하는 방법

※ Tip - 인텔리 J에서 메소드의 위치를 바꾸고 싶으면 ctrl + shift 방향키 위아래를 누르면 된다.

 

 

테스트 코드

@Test
public void dynamicQuery_whereParam() throws Exception {
    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(builder)
            .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 값은 무시된다.
  • 메서드를 다른 쿼리에서도 재활용할 수 있다(주목할 장점!!)
  • 쿼리 자체의 가독성이 높아진다.

ageParam = null 기준 SQL

where 다중 파라미터를 사용해도 null값이 자동으로 빠지는 것을 확인할 수 있다.

 

테스트 코드 - 메소드 조합

isValid로 이름이 적합한지, 또 날짜가 적합한지 만든 메소드(BetweenIn 오타가 있다)
이런 식으로 재조합한 메서드를 사용할 수 있어 편하다.

 

다른 예시

private Predicate allEq(String usernameCond, Integer ageCond){
    return usernameEq(usernameCond).and(ageEq(ageCond));
}
  • null 체크에 주의해야 한다. 


[6] 수정, 삭제 벌크 연산

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

@Test
@Commit
public void bulkUpdate() throws Exception {
    //member1 = 10 => 비회원
    //member2 = 20 => 비회원
    //member3 = 30 => 유지
    //member4 = 40 => 유지

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

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

    List<Member> result = queryFactory.selectFrom(member).fetch();
    for (Member member1 : result) {
        System.out.println("member1 = " + member1);
    }

}
  • 28세보다 어린 사람은 이름을 비회원으로 수정
  • @Rollback(false)와 @Commit은 사실 같은 코드다.
    • test에서 @Transactional을 하면 자동으로 롤백되는데, 실제로 DB에 들어갔는지 확인하려면 롤백을 하지 않도록 바꿔야 한다.
  • 벌크 연산은 영속성 컨텍스트를 무시하고 DB에 날리므로 항상 실행 후 영속성 컨텍스트를 비워줘야 한다.

 

■ 숫자 곱하기

@Test
public void bulkMultiple() throws Exception {
    long count = queryFactory
            .update(member)
            .set(member.age, member.age.multiply(2))
            .execute();
}
  • 곱셉은 .age.multiply()를 쓰고 원하는 배수를 () 안에 넣으면 된다.
  • 더하기는 .age.add(1)로 해주면 된다.
  • 빼기는 .age.add(-1)로 해주면 된다.

 

■ 벌크 연산 삭제하기

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

 

 

[7] SQL function 호출하기

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

■ Member → m으로 변경하기

@Test
public void sqlFunction() throws Exception {
    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);
    }

}

 

실제로 H2Dialect에 replace가 등록돼 있으며 필요하면 추가도 가능하다.

 

■ 소문자로 변경

@Test
public void sqlFunction2() throws Exception {
    List<String> result = queryFactory
            .select(member.username)
            .from(member)
            //.where(member.username.eq(
            //          Expressions.stringTemplate("function('lower', {0})", member.username)))
            .where(member.username.eq(member.username.lower()))
            .fetch();

    for (String s : result) {
        System.out.println("s = " + s);
    }
}

① .where(member.username.eq(Expressions.stringTemplate("function('lower', {0})", member.username)))로 사용하는 방법

② lower 같은 ansi 표준 함수는 querydsl이 내장하고 있으므로 username.lower()만 해줘도 된다.