본문 바로가기
Spring

JPA의 정적쿼리와 동적쿼리의 차이점

by 이숴 2023. 6. 6.
반응형

JPA를 쓰면서 개발을 할때마다 언제 정적쿼리로 작성해야할지, 동적쿼리로 작성을 해서 문제를 해결해야할지 고민되는 상황이 많습니다.

 

저도 매번 성능상 어느편이 좋은지 고민을 많이 하는데요.

 

사실 정적쿼리와 동적쿼리의 차이점을 제대로 알고있지 않은 부분도 있어서 쉽게 해결책을 찾지 못했던 것 같습니다.

 

따라서 이번 게시물에서는 정적쿼리와 동적쿼리의 차이점을 알아보고, 상황에 따른 쿼리 사용법을 확인해보겠습니다.

 

정적쿼리

정적쿼리란 애플리케이션 로딩 시점에서 JPQL로 작성된 쿼리를 사용합니다.

 

이쿼리가 의미하는 것은 애플리케이션이 실행되기 전에 이미 결과가 결정되어 변경되지 않는 쿼리를 말합니다.

 

예시를 보며 확인해보겠습니다.

@Entity
public class User {
    @Id
    private Long id;
    private String name;
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.name = ?1")
    List<User> findByName(String name);
}

위 코드를 보시면 특정 사용자 이름을 가진 모든 사용자를 조회하는 쿼리가 선언되어있습니다. 위 쿼리는 상황에따라 결과가 절대로 변하지 않고, 그저 사용자 이름에따라서만 쿼리가 생성이 되는데요.

 

이처럼 정적쿼리란 로딩 시점에 정의해놓고, 이후 런타임 때 쿼리의 구조 자체가 변경되지 않기 때문에 정적쿼리라고 할 수 있습니다.

 

여기서 여러 사람들이(저포함) 잘 몰랐던 부분이 있었다면, @Query 어노테이션이 사용된다고 해서 동적쿼리가 되는 것이 아니라, 정적쿼리일 경우에도 @Query 어노테이션이 사용될 수 있으므로 쿼리의 내용을 잘 확인해야한다는 것입니다.

(저만 몰랐던 사실일수도 있습니다..)

 

동적쿼리

다음으로 동적쿼리란 메소드의 실행 시점에 쿼리를 생성하거나 변경하는 것을 말합니다. JPA에서는 Criteria 또는 Querydsl 등등의 라이브러리를 사용해서 동적쿼리를 작성할 수도 있습니다.

 

코드를 보며 확인해보겠습니다. 아래 코드는 Querydsl로 작성된 동적쿼리 예시입니다.

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
import static project.trendpick_pro.domain.user.QUser.user;

@Repository
public class UserRepositoryCustom extends QuerydslRepositorySupport {

    private final JPAQueryFactory queryFactory;

    public UserRepositoryCustom(JPAQueryFactory queryFactory) {
        super(User.class);
        this.queryFactory = queryFactory;
    }

    public List<User> findByNameAndAge(String name, int age) {
        return queryFactory
                .selectFrom(user)
                .where(nameEq(name), ageGoe(age))
                .fetch();
    }

    private BooleanExpression nameEq(String name) {
        return hasText(name) ? user.name.eq(name) : null;
    }

    private BooleanExpression ageGoe(int age) {
        return (age > 0) ? user.age.goe(age) : null;
    }
}

코드를 보면 사용자의 이름과 나이를 가지고 동적으로 쿼리를 생성하는 코드입니다.

 

"어라? 사용자의 이름과 나이만을 가지고 쿼리를 작성하는 것은 정적쿼리로는 안되는 건가?" 라는 질문을 하실수도 있는데요.

 

위코드가 동적쿼리인 이유는 예를들어, 사용자의 이름과 나이를 검색 조건으로 받아 해당하는 사용자를 찾는 쿼리입니다.

 

이럴때, 입력받은 이름과 나이에 따라 where절의 조건이 변경되는 것을 말하는데요. 사용자의 이름과 나이 두가지 조건이 모두 있을 수도 있고 없을수도 있는 경우, 이름만 있는 경우, 나이만 있는경우, 이렇게 입력에 따라 쿼리의 구조가 달라지는 경우를 바로 동적쿼리라고 합니다.

 

하지만 보통 엔티티를 구성할때는 사용자의 이름과 나이는 nullable=false와 같이 제약조건이 붙게되는데요. 이러할 경우에는 무조건 두 데이터가 무조건 포함이 되야하는 것이기 때문에 쿼리가 정적쿼리가 된다고 할 수 있겠습니다. 그렇게 되면 기존과 같이 Spring Data JPA의 메소드만 선언하는 식으로 작성될 수 있겠죠.

``예시 1
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Query("SELECT u FROM User u WHERE u.name = :name AND u.age = :age")
    List<User> findByNameAndAge(@Param("name") String name, @Param("age") Integer age);
}

``예시2 
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findByNameAndAge(String name, Integer age);
}

 

*동적쿼리로 반환되었을 경우에는 이렇게 될 거같습니다.*

이름: String, 나이: null  => select m from Member m where m.name=:이름

이름: null, 나이: int  => select m from Member m where m.age=:나이

이름: String, 나이: int =>  select m from Member m where m.name=:이름 and m.age=:나이

이름: null, 나이: null  => select m from Member m

 

결론

정말 놀랐습니다. 이때까지 Querydsl또는 @Query로 작성되는 쿼리는 무조건 동적쿼리라고 생각했거든요. 이러한 차이를 제대로 알지못하는 상태에서 계속 개발을 했다면, 얼마나 큰위험이 터졌을지 지금이라도 알게되어서 다행이라는 생각이 드네요. 물론 호들갑일 수도 있겠지만.. 오늘도 지식이 늘게되어서 기분이 좋습니다 ㅎㅎ

제약조건이 없는 데이터를 조회하는 경우에 쿼리를 작성하게 되면 실행단계의 조건에 따라 쿼리의 모양이 달라져 동적쿼리를 작성하게 되는 것이고,
제약조건이 달린 데이터를 확인할 경우에는 무조건 쿼리가 같기 때문에 정적쿼리가 되는 것이다.

 

반응형

댓글