CH03 기본문법
jpql vs querydsl
컴파일 타임에 오류를 잡아준다.(jpql의 경우 쿼리를 문자열로 만들기 때문에 오류가 있을시 런타임에 알 수 있다.)
파라미터 바인딩을 쉽게 처리해준다.(jpql에서는 직접 문자열 쿼리 내에 :username 과 같이 삽입했다.)
@Test
public void startJPQL() {
String qlString =
"select m from Member m where m.username = :username";
Member findMember = em.createQuery(qlString, Member.class)
.setParameter("username", "member1")
.getSingleResult();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void startQuerydsl() {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
// QMember m = new QMember("m");
// Member findMember = queryFactory
// .select(m)
// .from(m)
// .where(m.username.eq("member1"))
// .fetchOne();
JPQLQuery<Member> query = queryFactory.selectFrom(member)
.where(member.username.eq("member1"));
Member findMember = query.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}JPAQueryFactory를 필드로
동시성 문제는 JPAQueryFactory를 생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은 EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는 걱정하지 않아도 된다.
즉, JPAQueryFactory를 를 필드로 빼고 이를 공유해도 JPAQueryFactory 에 사용되는 EntityManager 에서 알아서 어느 트랜잭션에 걸려있는 처리인지에 따라 다른 영속성 컨텍스트를 제공하기 때문에 동시성 관리가 되고 있어서 괜찮다고 할 수 있다.
동시성 문제는 걱정하지 않아도 된다. 왜냐하면 여기서 스프링이 주입해주는 엔티티 매니저는 실제 동작 시점에 진짜 엔티티 매니저를 찾아주는 프록시용 가짜 엔티티 매니저이다. 이 가짜 엔티티 매니저는 실제 사용 시점에 트랜잭션 단위로 실제 엔티티 매니저(영속성 컨텍스트)를 할당해준다. 더 자세한 내용은 자바 ORM 표준 JPA 책 13.1 트랜잭션 범위의 영속성 컨텍스트를 참고하자.
기본 Q_Type 활용
Q클래스 인스턴스를 사용하는 2가지 방법
Q 클래스 내부에 static 필드를 static import 해서 아래와 같이 바로 사용 하는 것을 권장한다.
기본 검색 쿼리(where 절 활용)
기본적인 where 조건 검색 쿼리는 아래와 같이 사용 가능하다.
여기서 where 절이 위와 같이 and 로 체이닝 걸려도 되고, 콤마를 통해서 varargs 로 넘겨줘도 똑같이 and 조건으로 쿼리가 실행된다. 이유는 아래와 같이 내부적으로 구현되어 있기 때문이다.
여기서 또 한 가지 주목할 점은 findMember3 처럼 null 을 넣어줘도 알아서 내부적으로 querydsl 이 무시해준다는 것이다. 이걸 활용하면 동적쿼리를 더 편하게 작성할 수 있다. 이 부분은 후에 동적쿼리를 다룰때 더 자세히 다룬다.
아래는 그 외 다양한 조건 표현식들이다.
결과조회
fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne() : 단 건 조회
결과가 없으면 : null
결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount() : count 쿼리로 변경해서 count 수 조회
정렬
페이징
실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만, count 쿼리는 조인이 필요 없는 경우도 있다. 그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두 조인을 해버리기 때문에 성능이 안나올 수 있다. count 쿼리에 조인이 필요없는 성능 최적화가 필요하다면, count 전용 쿼리를 별도로 작성해야 한다.
페이징을 할 때 여러 개의 join 이 들어간 쿼리가 실행될 일이 많은데, 사실 count 는 단순하게도 가능한 경우가 있는데 이 때 성능 향상을 위해서 count 는 자동생성된 쿼리로 사용하지 말고(join 이 들어간 쿼리) 직접 단순한 쿼리를 만들어서 사용하라는 것.
집합
집합함수
GroupBy
조인(기본조인)
join() , innerJoin() : 내부 조인(inner join)
leftJoin() : left 외부 조인(left outer join)
rightJoin() : rigth 외부 조인(rigth outer join)
조인(on 절)
ON절을 활용한 조인(JPA 2.1부터 지원)
on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.
조인(페치조인)
아래는 내가 프로젝트에서 사용했던 fetch join 코드 예시
서브 쿼리
서브쿼리 예시는 더 많은데 (in 절 등) 정리는 생략한다.
Case 문
querydsl 을 사용하여 case 문을 사용할 수는 있으나, 저렇게 데이터를 변환하는 식의 로직을 DB 단에서 처리하는 것은 옳지 못하다. database 에서는 데이터를 조회하고 가져가는 것만 하고 저러한 데이터 가공 및 조작은 어플리케이션에서 처리하자.
Last updated