Querydsl
- Querydsl 정적 타입을 이용해서 SQL과 같은 쿼리를 생서앟ㄹ 수 있도록 해 주는 프레임워크이다.
- 타입에 안전한 방식으로 HQL 쿼리를 실행하기 위한 목적으로 만들어졌다.
- 도메인의 변경이 직접적으로 쿼리에 반영되고, 쿼리 작성 과정에서 코드 자동완성 기능을 사용함으로써 쿼리를 더 빠르고 안전하게 만들 수 있게 된다.
원칙
타입 안정성
- 도메인 타입의 프로퍼티를 반영해서 생성한 쿼리 타입을 이용해서 쿼리를 작성하게 된다. 또한, 완전히 타입에 안전한 방법으로 함수/메서드 호출이 이루어진다.
JPA도 java코드로 쿼리를 작성하는 criteria를 지원을 한다. 다만 필자가 queryDsl을 도입하게 된 이유는 바로 type safe라는 개념때문이다. 기존 솔루션은 쿼리 문자열을 string 으로 작성해서 orm 엔진으로 넘기는 방식인데 여기에는 큰 문제가 있다. 쿼리 문자열에 오타나 잘못 작성된 부분이 있는지 확인하기가 어렵다는 것이다. 프로그램을 실행후 해당 쿼리가 실행되는 로직이 돌아가기 전에는 쿼리에 잘못이 있는지 확인이 어렵다. 또 만약 데이터 베이스의 스키마가 변경된다면 프로그램 안에서 그 부분과 관련된 쿼리 문자열이 모두 수정되어야 한다. JPA의 criretia도 필드이름을 string으로 넘기는 방식이라 type safe한 방식이 아니다. 또한 문법이 꽤 복잡하다.
소프트웨어의 요구사항은 계속해서 바뀌고 개발도중에는 데이터베이스 스키마도 수시로 변경될 가능성이 있다. 그때마다 관련 쿼리가 문제 없는지 확인하는 과정은 지루하고 실수하기 쉽다. queryDsl을 사용한다면 Entity 도메인 객체에 수정이 일어나면 관련된 쿼리 코드에 컴파일 에러가 발생하게 된다. 컴파일 에러가 발생하는 코드를 수정하고 컴파일 에러가 없어진다면 적어도 소프트웨어 동작중에 쿼리가 잘못되어 에러가 발생하는 경우는 없다고 봐도 된다. 이런걸 Type safe 하다고 한다. 여기서 얻은 안정감으로 더욱 과감하게 적극적으로 소프트웨어의 품질 향상을 위한 수정이 가능하게 된다. 단위 테스트 코드를 작성하는 것과 비슷한 원리이다.
출처 : http://adrenal.tistory.com/23
일관성
- 기반 기술에 상관없이 쿼리 경로와 오퍼레이션은 모두 동일하며, Query인터페이스는 공통의 상위 인터페이스를 갖는다.
- 모든 쿼리 인스턴스는 여러 차례 재사용 가능하다. 쿼리 실행 이후 페이징 데이터와 프로젝션 정의는 제거된다.
JPA 쿼리
쿼리 타입 사용하기
- Querydsl을 이용해서 쿼리를 작서앟려면, 변수와 Query구현체를 생성해야 한다.
@Entity
public class customer {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName(String fn) {
firstName = fn;
}
public void setLastName(String ln) {
lastName = ln;
}
}
- Querydsl은 Customer와 동일한 패키지에 QCustomer라는 이름을 가진 쿼리 타입을 생성한다.
- Querydsl에서 Customer타입을 위한 정적 타입 변수로 QCustomer를 사용한다.
- QCustomer는 기본 인스턴스 변수를 갖고 있으며, 다음과 같이 정적 필드로 접근할 수 있다.
QCustomer customer = QCustomer.customer;
- 다음처럼 Customer변수를 직접 정의할 수도 있다.
QCustomer customer = new QCustomer("myCustomer");
쿼리
- JPA API를 사용하려면 다음과 같이 JPAQuery 인스턴스를 사용하면 된다.
JPAQuery query = new JPAQuery(entityManager);
- Hibernate를 사용한다면, HibernateQuery를 사용하면 된다.
HibernateQuery query = new HibernateQuery(session);
- firstName 프로퍼티가 Bob인 Customer를 조회하고 싶다면 다음의 쿼리를 사용한다.
QCustomer customer = Qcustomer.customer;
JPAQuery query = new JPAQuery(entityManager);
Customer bob = query.from(customer)
.where(customer.firstName.eq("Bob"))
.uniqueResult(customer);
-
from 메서드는 쿼리 대상(소스)을 지정하고, where부분은 필터를 정의하고, uniqueResult는 프로젝션을 정의하고, 1개 결과만 리턴하라고 지시한다.
-
여러 소스로부터 쿼리를 만들고 싶을 때
QCustomer customer = QCustomer.customer;
QCompany company = QCompany.company;
uqery.from(customer, company);
- 여러 필터를 사용하는 방법
query.from(customer)
.where(customer.firstName.eq("Bob")
, customer.lastName.eq("Wilson"));
//또는
query.from(customer)
.where(customer.firstName.eq("Bob")
.and(customer.lastName.eq("Wilson")));
//'or'로 하고싶을 때
query.from(customer)
.where(customer.firstName.eq("Bob")
.or(customer.lastName.eq("Wilson")));
조인
- Querydsl은 JPQL의 이너 조인, 조인, 레프트 조인, 풀조인을 지원한다.
QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCat kitten = new QCat("kitten");
query.from(cat)
.innerJoin(cat.mate, mate)
.leftJoin(cat.kittens, kitten)
.list(cat);
//JPQL로 변환
from Cat as cat
inner join cat.mate as mate
left outer join cat.kittens as kitten
query.from(cat)
.leftJoin(cat.kittens, kitten)
.on(kitten.bodyWeight.gt(10.0))
.list(cat)
//JPQL로 변환
from Cat as cat
left join cat.kittens as kitten
on kitten.bodyWeight > 10.0
정렬
QCustomer customer = QCustomer.customer;
query.from(customer)
.orderBy(customer.lastName.asc()
,customer.firstName.desc())
.list(customer);
//JPQL로 변환
from Customer as customer
order by customer.lastName asc, customer.firstName desc
그룹핑
query.from(customer)
.groupBy(customer.lastName)
.list(customer.lastName);
//JPQL로 변환
select customer.lastName
from Customer as customer
group by customer.lastNameß
DeleteClause
QCustomer customer = QCustomer.customer;
//delete all customers
new JPADeleteClause(entityManager, customer).execute();
//delete all customers with a level less than 3
new JPADeleteClause(entityManager, customer)
.where(customer.level.lt(3))
.execute();
- JPADeleteClause 생성자의 두번째 파라미터는 삭제할 엔티티 대상이다.
- where는 필요에 따라 추가할 수 있으며, execute를 실행하면 삭제를 수행하고 삭제된 엔티티의 개수를 리턴한다.
UpdateClause
QCustomer customer = QCustomer.customer;
//rename customers named Bob to Bobby
new JPAUpdateClause(session, customer)
.where(customer.name.eq("Bob"))
.set(customer.name, "Bobby")
.execute();
- JPAUpdateClause 생성자의 두번째 파라미터는 수정할 엔티티 대상이다.
- set은 SQL의 update 스타일로 프로퍼티 수정을 정의하고, execute를 실행하면 수정를 수행하고 삭제된 엔티티의 개수를 리턴한다.
서브쿼리
- 서브쿼리를 만들기 위해 from 메서드로 쿼리 파라미터를 정의하고, unique나 list를 이용한다.
- unique는 단일 결과를 위해 사용하고 list는 리스트 결과를 위해 사용한다.
QDepartment department = Qdepartment.department;
QDepartment d = new QDepartment("d");
query.from(department)
.where(department.employees.size().eq(
new JPASubQuery().from(d).unique(d.employees.size.max())
)).list(department);
QEmployee employee = QEmployee.employee;
QEmployee e = new QEmployee("e");
query.from(employee)
.where(employee.weeklyhours.gt(
new JPASubQuery().from(employee.department.emplyees, e)
.where(e.manager.eq(employee.manager))
.unique(e.weeklyhours.avg())
)).list(employee);
원래의 JPA Query 구하기
JPAQuery query = new JPAQuery(entityManager);
Query jpaQuery = query.from(employee).createQuery(employee);
//...
List results = jpaQuery.getResultList();
출처 : http://www.querydsl.com/static/querydsl/3.4.3/reference/ko-KR/html_single/#jpa_integration