본문 바로가기
Today I Learned 2024. 6. 28.

(24.06.28)[11주차] QueryDSL과 SQL의 비교와 정리

QueryDSL, Query Domain Specific Language는 JPA 에서의 쿼리 전용 클래스로 Entity 의 매핑정도를 쿼리에 적합하도록 재구성을 해주는 기술로,

JPA를 통해 Java에서 DB의 데이터를 SQL 처럼 쿼리문과 유사하게 메서드를 사용해서 DB 값을 조작할 수 있도록 한다.

따라서 JPA 는 SQL의 DML과 매우 닮아있지만, 그렇다고 똑같이 사용하면 안되기 때문에

필수적이 메서드를 비교하면서 나름 찾아보면서 정리할 수 있도록 한다.


QueryDSL과 SQL

JPAQueryFactory

  • 재구성한 Q클래스를 객체/함수 를 사용해서 쿼리로 작성, 실행
  • 미리 Config를 통해서 EntityManager로 Bean 등록이 되어있다고 가정해서 아래 예제들을 활용한다고 생각할 수 있어야함!
@PersistenceContext
EntityManager em;
 
public List<User> selectUserByUsernameAndPassword(String username, String password){
	
	JPAQueryFactory jqf = new JPAQueryFactory(em);
	QUser user = QUser.user;
  
	List<Person> userList = jpf
                    .selectFrom(user)
                    .where(person.username.eq(username) // .eq()는 같은지 확인하는 QueryDSL 언어
                    .and(person.password.eq(password))
                    .fetch(); // 주어진 조건에 맞는 엔터티 값을 조회하고 결과를 가져온다는 의미

	return userList;
}
  • 가장 기본적인 QueryDSL문이지만, SQL에서는 사용하지 않는 eq(), fetch() 와 seletFrom 등이 존재
    • SQL의 기능을 JPA 에서 똑같이 할 수 있도록 만들어 낸 것으로 아래에 정리
    •  

주요 메서드

  • SQL의 DML, Data Manipulation Language 와 유사
    • 따라서 이하 메서드에 대한 어떻게 사용되는지에 대한 예제를 적으면서 어떻게 사용되는지에 대한 감을 익힐 수 있도록
    • 아래 메서드들의 jpf 는 JPAQueryFactory로 가정
더보기

select()

List<String> usernames = jqf.**select**(user.username)
                            .from(user)
                            .fetch();
  • 테이블의 특정 필드의 값을 선택하는 메서드

selectFrom()

List<User> users = jqf.selectFrom(user)
                      .where(user.username.eq("Andrew"))
                      .fetch();

  • 테이블에 있는 값 전체를 불러올 때 사용하는 메서드

where()

  • 위의 코드와 같이 조건을 부여할 때 사용

groupBy()

List<Tuple> results = jqf.select(user.role, user.count())
                         .from(user)
                         .groupBy(user.role)
                         .fetch();
  • SQL과 동일하게 그룹화 하는데 기준을 부여할 수 있음
  • Tuple 객체
    • 조회된 결과의 ROW 값을 담은 Container 역할의 객체
    • get 메서드를 통해서 ROW 의 특정 필드 값을 얻을 수 있음

orderBy()

List<User> users = jqf.selectFrom(user)
                      .orderBy(user.username.asc())
                      .fetch();
  • 정렬 기준과 방향을 지정해줄 수 있음

having()

List<Tuple> results = jqf.select(user.role, user.count())
                         .from(user)
                         .groupBy(user.role)
                         .having(user.count().gt(5))
                         .fetch();
  • SQL과 똑같이 groupBy로 그룹화할 때의 조건을 지정

distinct()

List<String> distinctUsernames = jqf.select(user.username)
                                    .distinct()
                                    .from(user)
                                    .fetch();
  • SQL과 똑같이 같은 값을 가지는 중복결과는 제외하는 메서드
    • SQL처럼 별도의 인자를 받지 않음

limit() offset()

List<User> users = jqf.selectFrom(user)
                      .limit(10)
                      .offset(20)
                      .fetch();
  • limit() 을 통해 SQL처럼 최대 반환할 값의 수를 지정
  • offset() 반환 시작하는 순서의 시작점을 지정, ()안에는 뛰어넘을 ROW의 수를 인자로 받음
    • 따라서 위의 코드는 user ROW 값들이 조회가 되는데, 21 번째부터 30번째의 값만 조회할 수 있음

JPAExpressions

// 서브쿼리 사용
QOrder order = QOrder.order;

List<User> users = jqf.selectFrom(user)
                      .where(user.id.in(
                          **JPAExpressions**.select(order.user.id)
                                        .from(order)
                                        .where(order.amount.gt(100))
                      ))
                      .fetch();

 // 서브쿼리의 결과를 사용
 return jqf.selectFrom(user)
                  .where(user.id.in(
                      **JPAExpressions**.select(order.user.id)
                                    .from(order)
                                    .where(order.amount.goe(amount))
                  ))
                  .fetch();
  • JPAExpressions : QueryDSL에서 서브쿼리를 작성할때 사용되어지는 유틸리티 클래스
    • 즉, 서브쿼리가 아니더라도, QueryDSL 에서 쿼리안에 쿼리가 들어가야할 때 사용

조건 메서드

  • QueryDSL 에서 where 조건절 등에서 데이터에 조건을 부여할때, SQL과 유사하게 작동시키기 위한 메서드들
  • SQL과 유사하나, 다른 점도 많기 때문에 한번씩 예제를 써가면서  익숙해질 필요가 있음
더보기

eq()

user.username.eq(username)
  • 해당 값과 같은지 비교

ne()

user.username.ne(username)
  • 해당 값과 다른지 비교

gt()

user.age.gt(18)
  • Greater Than 초과하는지에 대한 조건

lt()

user.age.lt(18)
  • Less Than 미만인지에 대한 조건

goe()

user.age.goe(18)
  • Greater Than or Equal 이상인지에 대한 조건
  • eq() 도 포함

loe()

user.age.loe(18)
  • Less Than or Equal 이하인지에 대한 조건

like()

user.username.like("%user%")
  • 해당 문자열을과 패턴이 맞는지에 대한 조건
  • SQL 문의 LIKE와 똑같으므로 패턴 역시 같이 사용

in()

user.role.in(Arrays.asList("ADMIN", "USER"))
  • 해당값이 in() 값 안에 포함하는지에 대한 조건
  • in은 Array 말고도 List Set 같은 컬렉션 타입에도 사용 가능

isNull()

user.address.isNull()
  • 값이 null 즉, DB 값이 없을 경우인지에 대한 조건

isNotNull()

user.address.isNotNull()
  • 값이 null 이 아닌지, 즉 값이 있는지에 대한 조건

조건 연결용 메서드

  • 쿼리 조건 연결과 똑같음

and()

user.username.eq(username).**and**(user.password.eq(password))

or()

user.username.eq(username)**.or**(user.email.eq(email))

fetch()

List<User> userList = jqf.selectFrom(user)
                         .where(user.username.eq(username)
                         .and(user.password.eq(password)))
                         **.fetch();**

  • 값을 반환할 수 있도록 하는 메서드

JPA에서의 JOIN

  • JOIN일 경우 SQL과 작동은 똑같이 하지만, 작성되는 규칙과 활용에서 조금 차이를 보이기 때문에 SQL 기준 INNER JOIN, CROSS JOIN 등 다양하게 비교하고 정리
더보기

INNER JOIN

// JPQL을 사용한 INNER JOIN
SELECT u FROM User u JOIN u.orders o WHERE ...

// QueryDSL을 사용한 INNER JOIN
queryFactory.selectFrom(user)
            .innerJoin(user.orders, order)
            .where(...);

  • JPA 에서는 JOIN 만 사용해서 수행이 가능
  • 메서드 체이닝을 이요해서 fetch join 도 사용 가능

LEFT JOIN

// JPQL을 사용한 LEFT JOIN
SELECT u FROM User u LEFT JOIN u.orders o WHERE ...

// QueryDSL을 사용한 LEFT JOIN
queryFactory.selectFrom(user)
            .leftJoin(user.orders, order)
            .where(...);

  • 그대로 leftjoin() 같기 사용을 할 수 있음
  • 단, RIGHT JOIN 은 지원하지 않으므로 필요에 의해 엔터티 순서를 바꿀 수 있도록 해야함

CROSS JOIN

// JPQL을 사용한 CROSS JOIN
SELECT u, p FROM User u JOIN Product p WHERE ...

// QueryDSL을 사용한 CROSS JOIN
queryFactory.selectFrom(user)
            .join(product)
            .where(...);

  • JPA에서는 join의 기본은 INNER JOIN 이 아닌 CROSS JOIN
    • 따라서 innerJoin()과 구분해서 쓸 수 있어야함

FETCH JOIN

// QueryDSL을 사용한 페치 조인
queryFactory.selectFrom(user)
            .leftJoin(user.orders, order).**fetchJoin()**
            .fetch();
  • JPQL에서도 사용하는 메인 엔터티와 연관된 엔터티를 한번의 쿼리로 로드하기 위해 사용하는 JOIN 문
    • JPA 의 특수 개념으로 SQL에는 없지만, JPA, Hibernate와 같은 ORM Framework에서 사용
  • JPA의 N+1 문제의 해결하고 성능을 최적화 하기 위해 사용
    • N+1 은 fetch type이 LAZY 이거나 여러 엔터티간 관계가 크게 이어질 경우, 한 쿼리를 좋회해도 여러번의 쿼리를 조회해야만 하는 문제

다행히 SQLD 시험을 보면서 이미 알고 있는 SQL문을 활용하는 것이지만,

여러 출처를 가리지 않고 스스로 정리하면서 조금씩 다른 부분이 꽤 존재를 하기 때문에 강조해야하는 부분은 Asterisk로 표시를 했다.

 

JpaRepository로도 활용할 수 있지만, 보다 직관적으로 복잡한 쿼리를 효율적으로 Java Spring 환경에서 사용할 수 있는 장점으로 많이 사용되어야할 부분으로 미리 정리할 수 있도록.