====== QueryDSL ======
* http://www.querydsl.com/
* [[java:jpa|JPA]] 쿼리를 더 편리하게 생성할 수도 있고
* [[java:querydsl:nativesql|QueryDSL Native SQL]]로 Native SQL도 생성 가능하다.
* [[http://www.querydsl.com/static/querydsl/latest/reference/html/|QueryDSL Reference]]
* Querydsl is a framework which enables the construction of type-safe SQL-like queries for multiple backends including JPA, JDO and SQL in Java.
* [[java:jpa:blaze_persistence|Blaze Persistence]] 도 참조.
* [[https://github.com/infobip/infobip-spring-data-querydsl|infobip-spring-data-querydsl]] : 각종 SpringDataXXX 프로젝트에 querydsl 연동
===== 프로젝트 이관 =====
* 5.x 이후에 https://github.com/OpenFeign/querydsl 으로 프로젝트과 이관됐다.
* 이로인해 ''groupId'' 도 ''io.github.openfeign.querydsl''로 변경되었다.
* https://mvnrepository.com/artifact/io.github.openfeign.querydsl
===== Q-Types multi thread loading 시 dead lock =====
* [[http://www.querydsl.com/static/querydsl/3.2.2/reference/html/ch04s02.html|4.2. Multithreaded initialization of Querydsl Q-types]]
// 미리 초기화를 호출하는게 좋다. packageToLoad 를 변경해준다.
ClassPathUtils.scanPackage(Thread.currentThread().getContextClassLoader(), packageToLoad);
===== join 성능 향상 =====
* Join 시 그 결과로 나오는 관계 객체를 Eager Loading 하려면 ''fetch()'' 를 사용해야 한다.
from(parent).innerJoin(parent.child).fetch()
....
===== JPA QueryDSL Cross Join 발생 =====
* ''Parent - Child''가 1:1 관계일 때 다음과 같이 쿼리했더니 inner join과 cross join이 함께 발생했다.
QParent parent = QParent.parent;
QChild child = parent.child;
from(parent).innerJoin(parent.child).fetch()
.where(parent.something.gt(parent.child.somthing))....;
* 다음처럼 ''innerJoin''에 PATH를 지정했더니 cross join이 사라졌다.
QParent parent = QParent.parent;
QChild child = QChild.child; // 여기 달라짐
from(parent).innerJoin(parent.child, child).fetch() // as 처리가 필요함.
.where(parent.something.gt(child.somthing))....;
===== JPA Subquery & JPAExpressions =====
* JPA Subquery 는 ''JPAExpressions.select'' 를 이용하여 각종 Sub Query 생성가능.
* SELECT 에서 사용할 때는 ''ExpressionUtils.as([subquery])'' 로 묶어준다.
===== Native 조건 condition =====
* [[http://www.querydsl.com/static/querydsl/latest/apidocs/com/mysema/query/support/Expressions.html|Expressions]]의 ''xxxTemplate''를 통해서 native 쿼리 조건을 만들어 낼 수 있다.
* JPA 사용시에는 해당 함수가 [[java:hibernate|Hibernate]] dialect에 함수(''registerFunction'')로 등록돼 있어야 한다.
// MySQL의 TIMESTAMPDIFF를 Expression으로 만들기
Expressions.numberTemplate(Long.class,
"TIMESTAMPDIFF(HOUR, {0}, {1})",
QSomething.dateField,
QOtherThing.anotherDateField);
// 이것을 메소드로 빼면 다음과 같은 형태가 된다.
public static NumberExpression hourDiff(
Expression extends Date> left,
Expression extends Date> right) {
return Expressions.numberTemplate(Long.class, "TIMESTAMPDIFF(HOUR, {0}, {1})", left, right);
}
* [[https://www.popit.kr/jpa-querydsl-group_concat-%ec%82%ac%ec%9a%a9%eb%b2%95/|JPA + Querydsl group_concat 사용법 | Popit]]
===== 상수가 들어가는 표현식 =====
* [[http://stackoverflow.com/questions/7661770/how-to-create-constant-numeric-value-expression-in-querydsl|java - How to create constant numeric value expression in QueryDSL? - Stack Overflow]]
* **''Expressions.constant(객체)''**는 Java 객체를 그에 맞는 JDBC 객체로 변환해준다.
// 0.12 - w.totalCost 를 나타내고자 한다.
Expressions.operation(Float.class, Ops.SUB,
Expressions.constant(0.12f), w.totalCost)
// or
NumberOperation.create(Float.class, Ops.SUB,
Expressions.constant(0.12f), w.totalCost)
===== 복잡한 쿼리 =====
* 복잡한 쿼리는 ''BooleanBuilder''를 사용한다.
public List getCustomer(String... names){
QCustomer customer = QCustomer.customer;
HibernateQuery qry = new HibernateQuery(session).from(customer);
BooleanBuilder builder = new BoolenBuilder();
for (String name : names){
builder.or(customer.name.eq(name));
}
qry.where(builder); // customer.name eq name1 OR customer.name eq name2 OR ...
return qry.list(customer);
}
===== Case/When =====
* [[https://jojoldu.tistory.com/401|[Querydsl] Case When 사용하기]]
* ''CaseBuilder'' 사용.
* ''CaseBuilder'' 사용시 ''when''과, ''otherwise'' 는 ''?'' 파라미터로 안먹고 무조건 상수로만 먹는듯 보임.
* 따라서, ''when''과 ''otherwise''가 DB 입장에서 봤을 때 동일한 타입이 되는 상수로 맞춰줘야 한다.
// registerDate 가 DATE 컬럼 일때
// newRegisterDate 는 LocalDateTime.class
Expression modifyDateTime = new CaseBuilder()
.when(qUser.registerDate.eq(expectedRegisterDate))
.then(qUser.registerDate) // DB 입장에서 DATE 컬럼
.otherwise(
// DB 입장에서 DATE 인 'TO_DATE' 함수를 사용해서 변경해줌. 상수값만
// 들어가기 때문에 어쩔 수 없이 문자로 모든 것을 넣어줌
Expressions.dateTimeTemplate(LocalDateTime.class, "to_date({0}, {1})",
newDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
Expressions.constant("yyyy-MM-dd HH:mm:ss"))
);
===== @Embeddable의 필드 소스 생성 =====
* Embeddable안에 또 다른 Embeddable이 있을 경우, 더 자식쪽 Embeddable의 경우 명시적 컬럼 매핑(''@Column'')이 없으면 모델 소스 (Q*) 생성시에 더 자식쪽 Embeddable의 필드에 대한 모델 소스가 올바르게 생성되지 않았다.
* 버그로 보인다.
==== Metamodel 생성 ====
* [[gradle:jpa_metamodel_generation|Gradle에서 JPA 2 MetaModel 생성]] 참조.
* [[gradle:querydsl_native_sql_codegen|Gradle 에서 Native SQL Code Generation]] 참조.
* Java 7에서 ''SimpleSerializerConfig.getConfig''에서 ''NullPointerException'' 발생하는 경우가 있는데 이는 QueryDSL과 아무 상관 없는 코드 중에 의존성에 들어가지 않는 Annotation을 넣은게 있었기 때문이었다(Java 8에서는 올바른 컴파일 오류 지점이 보였음).
Caused by: java.lang.NullPointerException
at com.mysema.query.codegen.SimpleSerializerConfig.getConfig(SimpleSerializerConfig.java:29)
at com.mysema.query.apt.DefaultConfiguration.(DefaultConfiguration.java:137)
at com.mysema.query.apt.jpa.JPAConfiguration.(JPAConfiguration.java:54)
...
===== 참고 =====
* [[http://www.javacodegeeks.com/2013/05/jpa-querydsl-projections.html|JPA - Querydsl Projections]]
* [[http://www.youtube.com/playlist?list=PLHETc3O85eXLvYt6lqex4kVBdMjjY9fLb|김영한님의 QueryDSL 소개 동영상]]
* [[http://luisfpg.blogspot.kr/2013/02/the-beauty-of-querydsl-in-sorting.html|Free IT / Luis Fernando Planella Gonzalez: The beauty of Querydsl: calling database functions]] 사용자 정의 함수 호출 기법
* [[http://blog.mysema.com/2011/08/querydsl-in-wild.html|Mysema Blog: Querydsl in the wild]]
* [[https://examples.javacodegeeks.com/enterprise-java/spring/spring-querydsl-tutorial/|Spring Querydsl Tutorial]]