Querydsl은 JPA, JDO, Mongodb 모듈에서 코드 생성을 위해 자바6의 APT 어노테이션 처리 기능을 사용한다. 이 절에서는 코드 생성을 위한 다양한 설정 옵션과 APT에 대한 대안을 설명한다.
기본적으로 Querydsl은 처음 2레벨의 레퍼런스 프로퍼티만 초기화한다. 더 깊은 경로로 초기화해야 한다면,
com.mysema.query.annotations.QueryInit
애노테이션을 도메인 타입에 적용해야 한다.
더 깊은 레벨로 초기화가 필요한 프로퍼티에 QueryInit 어노테이션을 적용한다. 다음은 적용 예를 보여주고 있다.
@Entity class Event { @QueryInit("customer.address") Account account; } @Entity class Account{ Customer customer; } @Entity class Customer{ String name; Address address; // ... }
이 예제는 Event 경로가 루트 경로인 /로 초기화될 때, account.customer 경로의 초기화를 실행한다. 경로 초기화 포맷은 와일드카드 문자(customer.* 또는 그냥 * 등)를 지원한다.
자동 경로 초기화는 수동 초기화를 대신하며, 엔티티 필드가 final이어선 안 된다. 선언적 포맷은 쿼리 타입의 모든 최상위 레벨 인스턴스에 적용할 수 있고 final 엔티티 필드의 사용을 가능하게 해주는 이점이 있다.
선호하는 초기화 방식은 자동 경로 초기화지만, 다음에 설명할 Config 어노테이션을 이용해서 수동 초기화를 활성화시킬 수 있다.
패키지나 타입에 Config 어노테이션을 사용해서 Querydsl의 직렬화를 커스터마이징할 수 있다. Querydsl은 어노테이션이 적용된 패키지와 타입의 직렬화 방식을 변경한다.
직렬화 옵션은 다음과 같다.
표 3.1. Config 옵션
이름 | 설명 |
---|---|
entityAccessors | public final 필드 대신 엔티티 경로로 사용할 접근 메서드 (기본값: false) |
listAccessors | listProperty(int index) 형식의 메서드 (기본값: false) |
mapAccessors | mapProperty(Key key) 형식의 접근 메서드 (기본값: false) |
createDefaultVariable | 기본 변수 생성 (기본값: true) |
defaultVariableName | 기본 변수의 이름 |
다음은 몇 가지 예이다.
엔티티 타입 직렬화 커스터마이징::
@Config(entityAccessors=true) @Entity public class User { //... }
엔티티 타입 직렬화 커스터마이징::
@Config(listAccessors=true) package com.mysema.query.domain.rel; import com.mysema.query.annotations.Config;
만약 직렬화 설정을 글로벌하게 변경하고 싶다면, 다음의 APT 옵셥을 사용하면 된다.
표 3.2. APT 옵션
이름 | 설명 |
---|---|
querydsl.entityAccessors | 레퍼런스 필드 접근 활성화 |
querydsl.listAccessors | 인덱스 이용한 리스트 직접 겁근 활성화 |
querydsl.mapAccessors | 키 기반 맵 직접 접근 활성화 |
querydsl.prefix | 쿼리 타입을 위한 접두어 (기본값: Q) |
querydsl.suffix | 쿼리 타입을 위한 접미사 |
querydsl.packageSuffix | 쿼리 타입 패키지를 위한 접미사 |
querydsl.createDefaultVariable | 기본 변수 만들지 여부 |
querydsl.unknownAsEmbeddable | 애노테이션 비적용 클래스를 embeddable로 처리할지 여부 (기본값: false) |
querydsl.includedPackages | 코드 생성에 포함될 패키지 목록 (콤마로 구분) (default: all) |
querydsl.includedClasses | 코드 생성에 포함될 클래스 이름 목록 (콤마로 구분) (default: all) |
querydsl.excludedPackages | 코드 생성에서 제외할 패키지 이름 (콤마로 구분) (default: none) |
querydsl.excludedClasses | 코드 생성에서 제외할 클래스 이름 (콤마로 구분) (default: none) |
다음은 메이븐 APT 플러그인 옵션 사용 예다.
<project> <build> <plugins> ... <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.0.9</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor> <options> <querydsl.entityAccessors>true</querydsl.entityAccessors> </options> </configuration> </execution> </executions> </plugin> ... </plugins> </build> </project>
경로 타입을 바꾸고 싶으면 커스텀 타입 매핑을 사용하면 된다. 특정 String 경로에 대해 비교나 String 연산을 막아야 하거나 커스텀 타입을 위해 Date/Time 지원이 추가되어야 하는 경우에 커스텀 타입 매핑을 유용하게 사용할 수 있다. Joda time API와 JDK(java.util.Date, Calendar 그리고 하위 타입)의 시간 타입은 기본으로 지원하며, 다른 API가 필요할 경우 이 기능을 사용하면 된다.
다음은 예시다.
@Entity public class MyEntity{ @QueryType(PropertyType.SIMPLE) public String stringAsSimple; @QueryType(PropertyType.COMPARABLE) public String stringAsComparable; @QueryType(PropertyType.NONE) public String stringNotInQuerydsl; }
PropertyType.NONE
은 쿼리 타입 생성시 프로퍼티를 생략할 때 사용된다.
@Transient나 @QueryTransient 어노테이션이 적용된 프로퍼티가 영속 대상에서 빠지는 것과 차이가 난다.
PropertyType.NONE은 단지 Querydsl 쿼리 타입에서 해당 프로퍼티를 제외한다.
정적 메서드를 위임 메서드로 선언하려면, 해당하는 도메인 타입을 값으로 갖는 QueryDelegate 어노테이션을 정적 메서드에 적용하고, 정적 메서드의 첫 번째 파라미터로 해당하는 Querydsl 쿼리 타입을 제공한다.
다음은 예제다.
@QueryEntity public static class User{ String name; User manager; }
@QueryDelegate(User.class) public static BooleanPath isManagedBy(QUser user, User other){ return user.manager.eq(other); }
QUser 쿼리 타입의 생성된 메서드는 다음과 같다.
public BooleanPath isManagedBy(QUser other) { return com.mysema.query.domain.DelegateTest.isManagedBy(this, other); }
내장 타입을 확장하는데 위임 메서드를 사용할 수도 있다. 다음은 몇 가지 예제다.
public class QueryExtensions { @QueryDelegate(Date.class) public static BooleanExpression inPeriod(DatePath<Date> date, Pair<Date,Date> period){ return date.goe(period.getFirst()).and(date.loe(period.getSecond())); } @QueryDelegate(Timestamp.class) public static BooleanExpression inDatePeriod(DateTimePath<Timestamp> timestamp, Pair<Date,Date> period){ Timestamp first = new Timestamp(DateUtils.truncate(period.getFirst(), Calendar.DAY_OF_MONTH).getTime()); Calendar second = Calendar.getInstance(); second.setTime(DateUtils.truncate(period.getSecond(), Calendar.DAY_OF_MONTH)); second.add(1, Calendar.DAY_OF_MONTH); return timestamp.goe(first).and(timestamp.lt(new Timestamp(second.getTimeInMillis()))); } }
내장 타입을 위한 위임 메서드를 선언하면, 위임 메서드를 알맞게 사용하는 하위 클래스가 만들어진다.
public class QDate extends DatePath<java.sql.Date> { public QDate(BeanPath<? extends java.sql.Date> entity) { super(entity.getType(), entity.getMetadata()); } public QDate(PathMetadata<?> metadata) { super(java.sql.Date.class, metadata); } public BooleanExpression inPeriod(com.mysema.commons.lang.Pair<java.sql.Date, java.sql.Date> period) { return QueryExtensions.inPeriod(this, period); } } public class QTimestamp extends DateTimePath<java.sql.Timestamp> { public QTimestamp(BeanPath<? extends java.sql.Timestamp> entity) { super(entity.getType(), entity.getMetadata()); } public QTimestamp(PathMetadata<?> metadata) { super(java.sql.Timestamp.class, metadata); } public BooleanExpression inDatePeriod(com.mysema.commons.lang.Pair<java.sql.Date, java.sql.Date> period) { return QueryExtensions.inDatePeriod(this, period); } }
@QueryEntities 애노테이션을 만들면, 애노테이션이 적용되지 않은 타입에 대해서도 Querydsl 쿼리 타입을 생성하는 것이 가능하다. QueryEntities 애노테이션을 선택한 패키지에 넣고, value 속성에 복제할 클래스를 값으로 지정한다.
실제로 타입을 생성하려면 com.mysema.query.apt.QuerydslAnnotationProcessor
를 사용한다.
메이븐 설정 방법은 다음과 같다.
<project> <build> <plugins> ... <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.0.9</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.mysema.query.apt.QuerydslAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> ... </plugins> </build> </project>
어노테이션이 적용된 자바 소스를 사용할 수 없는 경우(예를 들어, Scala나 Groovy와 같은
다른 JVM 언어를 사용했거나, 바이트코드 조작을 이용해서 어노테이션을 추가한 경우 등),
GenericExporter
클래스를 사용해서 클래스패스에서 어노테이션이 적용된 클래스를
스캔하고 검색된 클래스를 위한 쿼리 타입을 생성할 수 있다.
GenericExporter를 사용하려면 querydsl-codegen 모듈을 의존에 추가해주어야 한다.
(더 정확하게는 com.mysema.querydsl:querydsl-codegen:${querydsl.version}
모듈)
다음은 JPA를 위한 예제다.
GenericExporter exporter = new GenericExporter(); exporter.setKeywords(Keywords.JPA); exporter.setEntityAnnotation(Entity.class); exporter.setEmbeddableAnnotation(Embeddable.class); exporter.setEmbeddedAnnotation(Embedded.class); exporter.setSupertypeAnnotation(MappedSuperclass.class); exporter.setSkipAnnotation(Transient.class); exporter.setTargetFolder(new File("target/generated-sources/java")); exporter.export(DomainClass.class.getPackage());
이 코드는 DomainClass의 패키지 및 그 하위패키지에 위치한 모든 JPA 애노테이션 적용 클래스를 찾아 target/generated-sources/java 디렉토리에 쿼리 타입을 생성한다.
querydsl-maven-plugin의 generic-export, jpa-exportㅡjdo-export 골을 통해 GenericExporter를 사용할 수 있다.
각 골은 Querydsl, JPA, JDO 어노테이션에 매핑된다.
설정 엘리먼트는 다음과 같다.
표 3.3. 메이븐 설정
타입 | 엘리먼트 | 설명 |
---|---|---|
File | targetFolder | 생성된 소스가 위치할 대상 폴더 |
boolean | scala | Scala 소스를 생성하려면 true (기본값: false) |
String[] | packages | 엔티티 클래스를 검색할 패키지 |
boolean | handleFields | 필드를 프로퍼티로 처리할 경우 true (기본값: true) |
boolean | handleMethods | getter를 프로퍼티로 처리할 경우 true (기본값: true) |
String | sourceEncoding | 생성할 소스 파일의 캐릭터 인코딩 |
boolean | testClasspath | 테스트 클래스패스를 사용하려면 true |
다음은 JPA 어노테이션이 적용된 클래스를 위한 예다.
<plugin> <groupId>com.mysema.querydsl</groupId> <artifactId>querydsl-maven-plugin</artifactId> <version>${querydsl.version}</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>jpa-export</goal> </goals> <configuration> <targetFolder>target/generated-sources/java</targetFolder> <packages> <package>com.example.domain</package> </packages> </configuration> </execution> </executions> </plugin>
위 메이븐 설정은 com.example.domain
및 그 하위 패키지의
JPA 애노테이션 적용 클래스를 찾아 target/generated-sources/java 디렉토리에 코드를 생성한다.
생성 후에, 직접 생성된 소스를 컴파일하려면 그 소스 폴더를 위한 compile
골을 사용하면 된다.
<execution> <goals> <goal>compile</goal> </goals> <configuration> <sourceFolder>target/generated-sources/scala</targetFolder> </configuration> </execution>
compile
골은 다음 설정 엘리먼트를 갖는다.
표 3.4. 메이븐 설정
타입 | 엘리먼트 | 설명 |
---|---|---|
File | sourceFolder | 소스를 생성할 소스 폴더 |
String | sourceEncoding | 소스의 캐릭터 인코딩 |
String | source | 컴파일러의 -source 옵션 |
String | target | 컴파일러의 -target 옵션 |
boolean | testClasspath | 테스트 클래스패스를 사용할 경우 true |
Map | compilerOptions | 컴파일러 옵션 |
sourceFolder
를 제외한 모든 옵션은 선택사항이다.
Scala 출력을 원하면, 다음 설정을 사용하자.
<plugin> <groupId>com.mysema.querydsl</groupId> <artifactId>querydsl-maven-plugin</artifactId> <version>${querydsl.version}</version> <dependencies> <dependency> <groupId>com.mysema.querydsl</groupId> <artifactId>querydsl-scala</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> </dependencies> <executions> <execution> <goals> <goal>jpa-export</goal> </goals> <configuration> <targetFolder>target/generated-sources/scala</targetFolder> <scala>true</scala> <packages> <package>com.example.domain</package> </packages> </configuration> </execution> </executions> </plugin>