Tools & Libraries/Querydsl

[Querydsl] Querydsl 중급문법 3 (동적 쿼리 - where절 이용, 벌크 연산)

대기업 가고 싶은 공돌이 2024. 9. 13. 03:42

동적쿼리

where 다중 파라미터 사용

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

    // 동적 쿼리로 이름이 member1이고 나이가 10살인 사람을 찾고 싶은 상황

    List<Member> result = searchMember2(usernameParam,ageParam);

    Assertions.assertThat(result.size()).isEqualTo(1);
}

 

동적 쿼리로 들어오는 이름과 나이 변수에 맞춰 다른 값을 리턴시켜보자.

 

private List<Member> searchMember2(String usernameCond, Integer ageCond){
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    return queryFactory.selectFrom(member)
            .where(usernameEq(usernameCond), ageEq(ageCond))
            .fetch();
}

 

where 절에 다중 파라미터를 사용하는 방식은 다음과 같이 아주 간단하게 코드를 작성할 수 있다.

 

물론 usernameEq, ageEq 같은 메소드를 만들어야 한다는 단점이 있긴 하다.

 

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

private Predicate usernameEq(String usernameCond) {
    if (usernameCond != null) {
        return member.username.eq(usernameCond);
    }
    else{
        return null;
    }
}

 

메소드는 위와 같다 여기서 Predicate는 querydsl의 조건식을 나타내는 인터페이스이다.

 

따라서 ageCond가 들어왔을 때 null이라면 null을 바로 반환하고, null이 아니라면 조건식을 반환한다.

 

.where(usernameEq(usernameCond), ageEq(ageCond))

 

반환된 조건식은 이 where 절에 들어가게 되는데,

 

만약 반환값이 null이라서 where 절에 null이 들어갔다면 자동으로 무시가 된다.

 

그 덕분에 동적 쿼리를 만들 수 있는 것이다.

 

age에 null을 넣고 쿼리를 실행시켜 보자

다음과 같이 where 절에 조건이 들어가지 않은 것을 확인할 수 있다.

 

조건 합친 메소드 만들기

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

이렇게 각각의 메소드를 합쳐 하나의 메소드로도 만들 수 있다.

 

여기선 반환형이 BooleanExpression으로 바뀌었는데

 

이 참에 Predicate와 BooleanExpression의 차이점을 알아보자.

 

더보기

Predicate는 인터페이스로서 Predicate를 상속받은 여러 조건식을 모두 아우를 수 있는 반환형이다.

유연성이 높다는 장점이 있다.

 

BooleanExpression은 Predicate의 하위 타입으로 논리적인(참. 거짓)조건을 표현하는데 특화된 클래스 이다.

 

BooleanExpression을 반환형으로 사용할 때는 해당 메서드가 오직 논리 조건을 반환한다는 것을 명확하게 표현할 수 있다.

 

또한 BooleanExpression은 같은 클래스끼리 and() 나 or() 연산이 가능하기에, 

추가 적인 논리 연산이 필요한 경우 클래스를 맞춰 주어야 한다.

 

(물론 Predicate는 인터페이스기 때문에 and나 or 메소드를 사용할 수 없다.)

 

따라서 위의 상황에서도 and 연산을 사용해야하기 때문에 BooleanExpression으로 반환형을 바꿔준 것이다.

 

위와 같이 메소드를 합치는 방법은 조건이 많아지면 많아질 수록 활용성이 높아진다.

 

정리

  1. where 절에 null 조건은 무시 되기에 동적 쿼리를 만들 수 있다.
  2. 한 번 만들어놓은 메소드는 다른 쿼리에서도 재활용이 가능하다.
  3. 쿼리 자체의 가독성이 높아진다.

벌크 연산

수정, 삭제, 배치 쿼리

기존 JPQL은 더티 체킹이라 하여,

엔티티에 변경이 일어나면 그를 감지하여 update문을 날린다.

 

하지만 이 더티 체킹은 엔티티 변경이 일어나는 매 건마다 update 문을 날리기 때문에

기능상 좋지 못 하다. 이를 극복하기 위한 벌크연산을 배워보자.

@Test
@Commit
public void bulkUpdate(){
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    // update 쿼리는 결과로 영향을 받은 행 수가 나온다.
    // 현재 나이가 20살 이하인 행은
    // member1 하나만 존재한다.

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

위와 같은 update 쿼리를 날리면 끝이다.

실행 결과도 잘 나온 것을 확인할 수 있다.

 

주의할 점

벌크 연산은 항상 조심해야할 것이 있다.

 

JPA는 항상 영속성 컨텍스트에 엔티티를 올려둔다.

 

현재 모든 멤버가 영속성 컨텍스트에 올라가 있는 상태다.

 

member 1 - > 10

member 2 - > 20

member 3 - > 30

member 4 - > 40

 

여기서 벌크연산은 영속성 컨텍스트를 무시한 채 DB에 바로 쿼리를 날린다.

따라서 DB의 상태와 영속성 컨텍스트의 상태가 달라진다.

 

이 상태에서 queryfactory.selectFrom(member).fetch;

로 값을 가져오면 영속성 컨텍스트에 있는 값이 반환된다.

 

따라서 벌크 연산 이후엔 em.flush, em.clear()를 해줘서 초기화를 시켜야 한다.

 

더보기

영속성 컨텍스트는 트랜잭션 단위로 유지되기 때문에
서비스단에서 한 트랜잭션 내부에 벌크 연산을 만들어 뒀다면 초기화 시킬 필요가 없긴 하다.

전체 값을 더하거나 빼고 싶을 경우

@Test
public void bulkAdd(){
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

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

 

위와 같이 add를 사용하면 된다. minus는 따로 존재하지 않기 때문에 -1을 add 해주는 식으로 진행해야한다. 

 

곱하기는 .multifly를 사용하면 된다.

삭제 연산

@Test
public void bulkDelete(){
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    long count = queryFactory
            .delete(member)
            .where(member.age.eq(10))
            .execute();
}

 

삭제는 위와 같이 delete를 이용하여 진행해주면 된다. 

 

참고: 김영한 실전! Querydsl