Querydsl로 통합검색 구현하기 (w. JPA)

Kai·2023년 1월 21일
0

JPA

목록 보기
3/5

🌠 Querydsl의 등장


Querydsl이 생겨난 이유에 대해서 공식문서에서는 위와 같이 이야기하고 있다.

내용을 요약하자면, 이렇다.

쿼리에 다양한 경우의 수에 의한 조건들이 붙기 시작하면, 이것을 순수한 문자열로 짜기에는 너무무 어렵다. 그래서 이러한 경우에도 type-safety한 쿼리를 구현하기 위해서 Queyrdsl이 태어났다.

다양한 경우의 수가 존재하는 쿼리는 주로 조회 또는 검색과 관련된 쿼리를 짤 때 많이 마주치게 된다.
사용자를 검색한다고 가정해보면, 이름이 뭐고, 성별이 뭐고, 나이가 몇 이상이고...등등 수 많은 조건부 조회기능이 필요하게 된다.

이러한 기능을 그 경우에 맞게 String을 조합하여 쿼리를 짜본 경험이 한 번이라도 있으면, Querydsl이 개발자에게 얼마나 큰 편안함을 가져다 주는지는 길게 말하지 않아도 공감할 것이다.

그래서 오늘은 Querydsl로 검색기능을 구현하는 방법에 대해서 바~로 알아보도록 하겠다 👍


🏗️ 준비 과정


JPA + Querydsl을 사용하기 위해서는 아래의 과정을 따른다.


1) JPA Entity 구현

@Entity를 활용해서 DB의 테이블과 매핑되는 Entity를 만들어주면 된다.
예시로 사용자의 이름, 나이, 이메일을 기록하는 user라는 테이블이 있고, 이에 대응하는 User라는 Entity가 있다면 아래와 같이 Entity를 구현하게 될 것이다.

import jakarta.persistence.*;

@Entity
public class User {

	private String name;
    private Integer age;
	private String email;

}

2) 각 Entity에 대한 Q클래스 생성

Q클래스는 Querydsl에서 사용하는 클래스인데, Entity당 하나씩 생성이 된다.
User라는 Entity가 있다면, QUser라는 Q클래스가 하나 생성되는 것이다.
생성하는 방법은 스프링 앱을 실행하거나, Gradle을 사용한다면, Gradle에 Querydsl플러그인을 추가해서 명령어를 통해서 생성할 수 있다.

결과적으로 Q클래스는 아래와 같이 생성되게 된다.
(실제 예시)


3) JPAQueryFactory 선언

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @PersistenceContext
    private EntityManager em;

    @Bean
    public JPAQueryFactory queryFactory() {
        return new JPAQueryFactory(em);
    }

}

그리고 JPAQueryFactory를 Bean으로 등록해주면, 모든 준비가 끝나게 된다.


🔥 통합 검색 기능 구현


@Entity
public class User {

	private String name;
    private Integer age;
	private String email;

}

위에서 언급했던 이름, 나이, 이메일을 기록하는 user라는 테이블이 있고, 이를 User라는 Entity로 구현했다고 가정해보자.

그리고, 사용자가 어떤 검색어로 검색을 하면 이름, 나이, 이메일중 하나라도 검색어를 포함하고 있는 데이터를 조회하는 기능을 구현해보자.

import static group.artifact.entity.user.QUser.user;

@Repository
public class UserRepository {

	@Autowired
    private JPAQueryFactory queryFactory;

    public List<User> search(String 검색어) {
        return queryFactory
        	.select(user)
            .from(user)
            .where(
            	user.name.contains(검색어)
            	.or(user.email.contains(검색어))
            	.or(user.age.contains(검색어))
            )
            .fetch();
    }

}

이 코드를 설명하자면, 아래와 같다.

  • JPAQueryFactory를 주입받아서 Querydsl로 쿼리를 빌드할 준비를 한다.
  • User에 대한 Q클래스를 static import로 가져온다.
  • .contains()메서드로 %{검색어}%와 같이 와일드카드를 활용한 where절을 만든다.

그래서 결론적으로 위의 .search()메서드가 실행이 되면 DB에는 아래와 같은 SQL이 실행이 되게 된다.

SELECT * FROM user 
WHERE name LIKE '%검색어%' 
OR email LIKE '%검색어%' 
OR age LIKE '%검색어%';

이로써 굉장히 읽기 쉽고 이해하기 쉽게 뚝딱 통합검색 기능이 만들어지게 된다. 🤭
이러한 구현방법은 DB나 필드들이 복잡해질 수록 빛날 것이다 ㅎㅎ


🤭 Querydsl의 진가


위에서는 WHERE절에 OR조건이 들어가는 경우를 이야기했는데, 사실 Querydsl은 AND조건일 때 더 빛난다 ㅎㅎ
왜냐하면, Querydsl로 AND조건을 구현할 때는 해당 조건이 NULL이면, DB에 쿼리를 실행할 때, 아예 그 조건을 빼버리기 때문이다.

public List<User> something() {
	return queryFactory
    	.select(user)
        .from(user)
		.where(condition1, condition2, condition3)
        .fetch();
}

예를 들어서 위와 같은 메서드를 실행할 때, 모든 조건이 NULL이 아니라면 1번 쿼리가 실행되고, condition2가 NULL이라면 2번 쿼리가 실행되게 된다는 것이다.

# 1번 쿼리 (모든 조건 포함)
SELECT * FROM user WHERE condition1 AND condition2 AND condition3;

# 2번 쿼리 (1개의 조건 제외)
SELECT * FROM user WHERE condition1 AND condition3;

이런식으로 조건에 맞게 쿼리를 다르게 실행하는 것은 성능 최적화를 위해 당연히 해주어야하는 부분인데, Qeurydsl을 사용하면 아주 직관적으로 이를 구현할 수 있게 된다.

또, 이러한 기능을 순수한 문자열로 쿼리를 짠다고 생각해보면... if문지옥에 바로 입수하게 될 것이다 ㅎㅎㅎ


☕ 마무리

나는 사실 개발에 입문할 때 부터 ORM이나 쿼리빌더와 같은 라이브러리들을 사용해와서 이런게 막 엄청 놀랍지는 않다. 그렇지만 '와... 이런 거 없으면 예전에는 개발 어케 했냐 도대체'라는 생각이 절로 들긴 한다 ㅎㅎㅎ
또, 고대(?)의 개발을 직접 겪지 않아도 Qeurydsl이 얼마나 혁신적이고 개발자에게 편안함을 가져다 주는지 잘 알것 같다 🤭

아무튼 Querydsl로 모두 행코하길 바라며 마치도록 하겠다. 🙏

2개의 댓글

comment-user-thumbnail
2023년 8월 3일

깔끔한 정리 잘 읽고 갑니다

1개의 답글