Project example

Setting up project with JPA, Querydsl and H2 as a database may seem daunting task for a newcomer, but it’s not really that difficult. I will show both Maven 3.x and Gradle 4.x projects using Java 8.

Querydsl and Maven

Maven is supported out-of-the-box with a plugin as documented here.

First we need to declare our dependencies (provided scope for APT JAR is enough).

Maven: Querydsl dependencies
 1 <dependency>
 2   <groupId>com.querydsl</groupId>
 3   <artifactId>querydsl-apt</artifactId>
 4   <version>${querydsl.version}</version>
 5   <scope>provided</scope>
 6 </dependency>
 7 
 8 <dependency>
 9   <groupId>com.querydsl</groupId>
10   <artifactId>querydsl-jpa</artifactId>
11   <version>${querydsl.version}</version>
12 </dependency>
13 
14 <dependency>
15   <groupId>org.slf4j</groupId>
16   <artifactId>slf4j-log4j12</artifactId>
17   <version>1.6.1</version>
18 </dependency>

Next we need to configure the Maven APT plugin.

Maven: Querydsl generator plugin
 1 <project>
 2   <build>
 3   <plugins>
 4     ...
 5     <plugin>
 6       <groupId>com.mysema.maven</groupId>
 7       <artifactId>apt-maven-plugin</artifactId>
 8       <version>1.1.3</version>
 9       <executions>
10         <execution>
11           <goals>
12             <goal>process</goal>
13           </goals>
14           <configuration>
15             <outputDirectory>target/generated-sources/java</outputDirectory>
16             <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
17           </configuration>
18         </execution>
19       </executions>
20     </plugin>
21     ...
22   </plugins>
23   </build>
24 </project>

This assures that the Q-classes will be generated during the build. There is also alternative processor to generate these from Hibernate annotations. Most example projects for this book have pom.xml files like this.

Unrelated to Querydsl is the plugin configuration for generating JPA metadata. Here you can use various providers to do so. In querydsl-basic/pom.xml I use EclipseLink’s annotation processor.

IDE support

As per instructions, for Eclipse to recognize the generated classes one should run:

1 mvn eclipse:eclipse

I have no idea how the regeneration of Q-classes works as I don’t use Eclipse.

In IntelliJ IDEA the generated classes are recognized when the Maven project is imported. To generate the classes after an entity source was changed we need to set up annotation processor in Settings > Build, Execution, Deployment > Compiler > Annotation Processors.

Querydsl and Gradle

Next I will show an example of Gradle project. I didn’t use any particular plugin for Querydsl although there are some out there, but the situation around Gradle and its plugins is much more volatile hence I decided to do it without a plugin. If you’re familiar with Gradle it should be easy to modify example scripts.

I experimented with various scripts in querydsl-basic project that – as mentioned above with Maven – generates also JPA metadata. Probably most typical Gradle script would be build-traditional.gradle – let’s go over it bit by bit:

 1 plugins {
 2   id 'java'
 3 }
 4 
 5 repositories {
 6   jcenter()
 7 }
 8 
 9 configurations {
10   querydslApt
11   jpaMetamodelApt
12 }

So far nothing special, java plugin will be sufficient, we just need additional configuration for each generate task (querydslApt and jpaMetamodelApt).

1 sourceCompatibility = 1.8
2 targetCompatibility = 1.8
3 
4 tasks.withType(JavaCompile) {
5   options.encoding = 'UTF-8'
6 }

Next we just configure Java version and encoding for compilation (this should always be explicit). Next we’ll set up dependencies:

 1 ext {
 2   querydslVersion = '4.1.4'
 3   hibernateVersion = '5.2.2.Final'
 4   eclipseLinkVersion = '2.6.2'
 5   h2Version = '1.4.190'
 6   logbackVersion = '1.2.3'
 7   testNgVersion = '6.11'
 8 }
 9 
10 dependencies {
11   compileOnly 'javax:javaee-api:7.0'
12   compile "com.querydsl:querydsl-jpa:$querydslVersion"
13   compile "org.hibernate:hibernate-entitymanager:$hibernateVersion"
14   compile "org.eclipse.persistence:org.eclipse.persistence.jpa:$eclipseLinkVersion"
15   compile "com.h2database:h2:$h2Version"
16   compile "ch.qos.logback:logback-classic:$logbackVersion"
17 
18   testCompile "org.testng:testng:$testNgVersion"
19 
20   querydslApt "com.querydsl:querydsl-apt:$querydslVersion"
21   jpaMetamodelApt "org.eclipse.persistence:" +
22     "org.eclipse.persistence.jpa.modelgen.processor:$eclipseLinkVersion"
23 }

Dependencies needed only for generator tasks are in their respective configurations – we don’t need these to run our programs after build.

 1 sourceSets {
 2   generated {
 3     java {
 4       srcDirs = ["$buildDir/generated-src"]
 5     }
 6   }
 7   test {
 8     // This is required for tests to "see" generated classes as well
 9     runtimeClasspath += generated.output
10   }
11 }

Here we defined new source set for generated classes. I’m not sure what’s the best practice, whether different set for each generator would be better or not and why, so I’ll leave it like this. Next the generator tasks – they are both very similar and definitely could be refactored and put into plugins, but that’s beyond the scope of this book and my current experience:

 1 task generateQuerydsl(type: JavaCompile, group: 'build',
 2   description: 'Generates the QueryDSL query types')
 3 {
 4   source = sourceSets.main.java
 5   classpath = configurations.compile + configurations.querydslApt
 6   options.compilerArgs = [
 7     '-proc:only',
 8     '-processor', 'com.querydsl.apt.jpa.JPAAnnotationProcessor'
 9   ]
10   destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
11 }
12 
13 task generateJpaMetamodel(type: JavaCompile, group: 'build',
14   description: 'Generates metamodel for JPA Criteria (not QueryDSL)')
15 {
16   source = sourceSets.main.java
17   classpath = configurations.compile + configurations.jpaMetamodelApt
18   options.compilerArgs = [
19     '-proc:only',
20     '-processor',
21     'org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor',
22     '-Aeclipselink.persistencexml=src/main/resources/META-INF/persistence.xml',
23     '-Aeclipselink.persistenceunits=demo-el'
24   ]
25   destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
26 }

Finally we want these tasks to interplay nicely with the rest of the build:

 1 compileJava {
 2   dependsOn generateQuerydsl
 3   dependsOn generateJpaMetamodel
 4   source generateQuerydsl.destinationDir
 5   source generateJpaMetamodel.destinationDir
 6 }
 7 
 8 compileGeneratedJava {
 9   dependsOn generateQuerydsl
10   dependsOn generateJpaMetamodel
11   options.warnings = false
12   classpath += sourceSets.main.runtimeClasspath
13 }
14 
15 test {
16   useTestNG()
17 }

That’s it. This all works, but I encountered a minor problem with Hibernate if we rely on entity automatic discovery. This happens only within a single JAR in which the persistence.xml was found. However, Gradle builds projects to a different structure than Maven and keeps resources and classes as separate directories – where a directory as a classpath entry effectively means separate JAR. So if you download the project go to manuscript/examples/querydsl-basic and run:

1 gradle -b build-traditional.gradle clean build

You will see a failure of DemoTest (very simple TestNG-based test class) for Hibernate test. This can be fixed by listing the classes in persistence.xml – which would actually be compliant with how JPA works in Java SE environment – or you need somehow make build/resources and build/classes be one.

This actually is not that complicated and that’s what gradle.build is like in the end (including a long explanation comment in it). All we need to do is add three lines into sources sets:

1 sourceSets {
2   // these three lines is the only difference against querydsl-traditional.build
3   main {
4     output.resourcesDir = "$buildDir/classes/java/main"
5   }
6   //... the rest is the same

This is a very subtle change, but very effective and I didn’t notice any other negative side effects. I can’t comment on the effect of this change on more complicated big projects though. I further tried to make even more drastic change and declare generated source directory as part of Java main source set:

1 sourceSets {
2   main {
3     // This is actually not recommended, read on...
4     java.srcDir file(generatedSrc)

This also works, sort of, but the build is not able to detect changes properly and always runs the generation, even when everything is up-to-date. You can try this build, it’s in build-extreme.gradle, but it seems to be obviously not well behaved Gradle build.

IntelliJ IDEA support

Gradle support in IntelliJ IDEA is obviously constantly getting better and depending on the used version of the tools (I used Gradle 4.0, 4.1 and 4.2 on IDEA 2017) it’s not always easy to figure out how well it should work. Recently I saw a breathtaking presentation of Kotlin DSL support in Gradle (build file named gradle.build.kts) but I simply wasn’t able to reproduce it on my projects build from scratch. The same I remember for reasonable support for Gradle DSL years ago, but this seems to be much better now.

We can either import an existing sources as new IDEA module from external Gradle model or create new Gradle module. In either case after Gradle project refresh IDEA understands the project model quite well. There is a Settings option for Gradle called “Create separate module per source set” which is enabled by default but I personally like it disabled to get just one IDEA module per demo (like with Maven).

There is also interesting settings in Gradle/Runner called “Delegate IDE build/run actions to gradle” and this one I actually prefer. This completely bypasses IDEA’s internal build and calls Gradle build for any build/compilation and to run application or tests as well.

As we discussed the hiccup with Hibernate’s scan for entities this affects IDEA’s internal build mechanism even with the right Gradle build. While with Maven IDEA builds into its target directory with Gradle IDEA uses its own out directory because reportedly Gradle’s and IDEA’s way of building are way different and would not work if they stepped on each other’s toes. This is exactly what that “Delegate…” setting option fixes. Builds are pretty fast (it’s Gradle after all!) and it feels good when you know that you use the same build in IDE and on your CI as well.