Today I Learned
(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 환경에서 사용할 수 있는 장점으로 많이 사용되어야할 부분으로 미리 정리할 수 있도록.
'Today I Learned' 카테고리의 다른 글
(24.07.03)[12주차] QueryDSL사용을 위한 Repository 분리 (0) | 2024.07.03 |
---|---|
(24.07.02)[12주차] QueryDSL의 Wildcard 와 Count (0) | 2024.07.02 |
(24.06.27)[11주차] JPA 심화 강의 02 (마무리) (0) | 2024.06.27 |
(24.06.26)[11주차] JPA 심화 강의 01 (0) | 2024.06.26 |
(24.06.25)[11주차] Spring Security의 User 권한 설정 (0) | 2024.06.25 |