Writing queries to access data is usually done using Java String
s. The query languages of
choice have been SQL for JDBC as well as HQL/JPQL for Hibernate/JPA.
Defining the queries in plain String
s is powerful but
quite error-prone, as it’s very easy to introduce typos. Beyond that,
there’s little coupling to the actual query source or sink, so column
references (in the JDBC case) or property references (in the HQL/JPQL
context) become a burden in maintenance because changes in the table or
object model cannot be reflected in the queries easily.
The Querydsl project tries to tackle this problem by providing a fluent API to define these queries. The API is derived from the actual table or object model but is highly store- and model-agnostic at the same time, so it allows you to create and use the query API for a variety of stores. It currently supports JPA, Hibernate, JDO, native JDBC, Lucene, Hibernate Search, and MongoDB. This versatility is the main reason why the Spring Data project integrates with Querydsl, as Spring Data also integrates with a variety of stores. The following sections will introduce you to the Querydsl project and its basic concepts. We will go into the details of the store-specific support in the store-related chapters later in this book.
When working with Querydsl, you will
usually start by deriving a metamodel from your domain classes. Although the library can
also use plain String
literals, creating the
metamodel will unlock the full power of Querydsl, especially its type-safe
property and keyword references. The derivation mechanism is based on the
Java 6 Annotation Processing Tool (APT), which allows for hooking into the
compiler and processing the sources or even compiled classes. For details,
read up on that topic in Generating the Query Metamodel.
To kick things off, we need to define a domain class like the one shown in
Example 3-1. We model our
Customer
with a few primitive and nonprimitive
properties.
Note that we annotate the class
with @QueryEntity
. This is
the default annotation, from which the Querydsl annotation processor
generates the related query class. When you’re using the integration with
a particular store, the APT processor will be able to recognize the
store-specific annotations (e.g., @Entity
for JPA) and use them to derive the query classes. As we’re not going to
work with a store for this introduction and thus cannot use a
store-specific mapping annotation, we simply stick with
@QueryEntity
. The generated Querydsl query
class will now look like Example 3-2.
Example 3-2. The Querydsl generated query class
@Generated
(
"com.mysema.query.codegen.EntitySerializer"
)
public
class
QCustomer
extends
EntityPathBase
<
Customer
>
{
public
static
final
QCustomer
customer
=
new
QCustomer
(
"customer"
);
public
final
QAbstractEntity
_super
=
new
QAbstractEntity
(
this
);
public
final
NumberPath
<
Long
>
id
=
_super
.
id
;
public
final
StringPath
firstname
=
createString
(
"firstname"
);
public
final
StringPath
lastname
=
createString
(
"lastname"
);
public
final
QEmailAddress
emailAddress
;
public
final
SetPath
<
Address
,
QAddress
>
addresses
=
this
.<
Address
,
QAddress
>
createSet
(
"addresses"
,
Address
.
class
,
QAddress
.
class
);
…
}
You can find these classes in the
target/generated-sources/queries
folder of the module’s sample project. The class exposes public
Path
properties and references to other
query classes (e.g., QEmailAddress
). This enables
your IDE to list the available paths for which you might want to define
predicates during code completion. You can now use these
Path
expressions to define reusable
predicates, as shown in Example 3-3.
Example 3-3. Using the query classes to define predicates
QCustomer
customer
=
QCustomer
.
customer
;
BooleanExpression
idIsNull
=
customer
.
id
.
isNull
();
BooleanExpression
lastnameContainsFragment
=
customer
.
lastname
.
contains
(
"thews"
);
BooleanExpression
firstnameLikeCart
=
customer
.
firstname
.
like
(
"Cart"
);
EmailAddress
reference
=
new
EmailAddress
(
"[email protected]"
);
BooleanExpression
isDavesEmail
=
customer
.
emailAddress
.
eq
(
reference
);
We assign the static
QCustomer.customer
instance to the customer
variable to be able to concisely refer to its property paths. As you can
see, the definition of a predicate is clean, concise, and—most
importantly—type-safe. Changing the domain class would cause the query
metamodel class to be regenerated. Property references that have become
invalidated by this change would become compiler errors and thus give us
hints to places that need to be adapted. The methods available on each of
the Path
types take the type of the
Path
into account (e.g., the
like(…)
method makes sense only on
String
properties and thus is provided only on
those).
Because predicate definitions are so
concise, they can easily be used inside a method declaration. On the other
hand, we can easily define predicates in a reusable manner, building up
atomic predicates and combining them with more complex ones by using
concatenating operators like And
and
Or
(see Example 3-4).
Example 3-4. Concatenating atomic predicates
QCustomer
customer
=
QCustomer
.
customer
;
BooleanExpression
idIsNull
=
customer
.
id
.
isNull
();
EmailAddress
reference
=
new
EmailAddress
(
"[email protected]"
);
BooleanExpression
isDavesEmail
=
customer
.
emailAddress
.
eq
(
reference
);
BooleanExpression
idIsNullOrIsDavesEmail
=
idIsNull
.
or
(
isDavesEmail
);
We can use our newly written predicates
to define a query for either a particular store or plain collections. As
the support for store-specific query execution is mainly achieved through
the Spring Data repository abstraction, have a look at Integration with Spring Data Repositories. We’ll use the feature to query
collections as an example now to keep things simple. First, we set up a
variety of Product
s to have something we can
filter, as shown in Example 3-5.
Example 3-5. Setting up Products
Product
macBook
=
new
Product
(
"MacBook Pro"
,
"Apple laptop"
);
Product
iPad
=
new
Product
(
"iPad"
,
"Apple tablet"
);
Product
iPod
=
new
Product
(
"iPod"
,
"Apple MP3 player"
);
Product
turntable
=
new
Product
(
"Turntable"
,
"Vinyl player"
);
List
<
Product
>
products
=
Arrays
.
asList
(
macBook
,
iPad
,
iPod
,
turntable
);
Next, we can use the Querydsl API to actually set up a query against the collection, which is some kind of filter on it (Example 3-6).
We’re setting up a Querydsl
Query
using the
from(…)
method, which is a static method on the
MiniAPI
class of the querydsl-collections module. We hand it an
instance of the query class for Product
as well as
the source collection. We can now use the
where(…)
method to apply predicates to the source
list and execute the query using one of the
list(…)
methods (Example 3-7). In our case, we’d simply like to get
back the Product
instances matching the defined
predicate. Handing $.description
into the
list(…)
method would allow us to project the
result onto the product’s description and thus get back a collection of
String
s.
As we have discovered, Querydsl allows us to define entity predicates in a concise and easy way. These can be generated from the mapping information for a variety of stores as well as for plain Java classes. Querydsl’s API and its support for various stores allows us to generate predicates to define queries. Plain Java collections can be filtered with the very same API.
As we’ve just seen, the core artifacts
with Querydsl are the query metamodel classes. These classes are generated via the
Annotation Processing
Toolkit, part of the javac
Java compiler in Java 6. The
APT provides an API to programmatically inspect existing Java source code
for certain annotations, and then call functions that in turn generate
Java code. Querydsl uses this mechanism to provide special APT processor
implementation classes that inspect annotations. Example 3-1 used Querydsl-specific annotations like
@QueryEntity
and
@QueryEmbeddable
. If we already have domain
classes mapped to a store supported
by Querydsl, then generating the metamodel classes will require no extra effort. The core
integration point here is the annotation processor you hand to the
Querydsl APT. The processors are usually executed as a build step.
To integrate with Maven, Querydsl
provides the maven-apt-plugin, with
which you can configure the actual processor class to be used.
In Example 3-8, we bind the
process
goal to the generate-sources
phase,
which will cause the configured processor class to inspect classes in
the src/main/java folder. To
generate metamodel classes for classes in the test sources (src/test/java), add an execution of the
test-process
goal to the generate-test-sources
phase.
Example 3-8. Setting up the Maven APT plug-in
<project
…
>
<build>
<plugins>
<plugin>
<groupId>
com.mysema.maven</groupId>
<artifactId>
maven-apt-plugin</artifactId>
<version>
1.0.2</version>
<executions>
<execution>
<goals>
<goal>
process</goal>
</goals>
<phase>
generate-sources</phase>
<configuration>
<outputDirectory>
target/generated-sources/java</outputDirectory>
<processor>
<!-- fully-qualified processor class name -->
</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Querydsl ships with a variety of APT processors to inspect different sets of annotations and generate the metamodel classes accordingly.
QuerydslAnnotationProcessor
The very core annotation processor inspects Querydsl-specific
annotations like @QueryEntity
and
@QueryEmbeddable
.
JPAAnnotationProcessor
Inspects javax.persistence annotations, such as @Entity
and
@Embeddable
.
HibernateAnnotationProcessor
Similar to the JPA processor but adds support for Hibernate-specific annotations.
JDOAnnotationProcessor
Inspects JDO annotations, such as
@PersistenceCapable
and
@EmbeddedOnly
.
MongoAnnotationProcessor
A Spring Data–specific processor inspecting the
@Document
annotation. Read more on this in The Mapping Subsystem.
Now that we have the query classes in place, let’s have a look at how we can use them to actually build queries for a particular store. As already mentioned, Querydsl provides integration modules for a variety of stores that offer a nice and consistent API to create query objects, apply predicates defined via the generated query metamodel classes, and eventually execute the queries.
The JPA module, for example, provides a
JPAQuery
implementation class that takes an EntityManager
and
provides an API to apply predicates before execution; see Example 3-9.
Example 3-9. Using Querydsl JPA module to query a relational store
EntityManager
entityManager
=
…
// obtain EntityManager
JPAQuery
query
=
new
JPAQuery
(
entityManager
);
QProduct
$
=
QProduct
.
product
;
List
<
Product
>
result
=
query
.
from
(
$
).
where
(
$
.
description
.
contains
(
"Apple"
)).
list
(
$
);
assertThat
(
result
,
hasSize
(
3
));
assertThat
(
result
,
hasItems
(
macBook
,
iPad
,
iPod
));
If you remember Example 3-6, this code snippet
doesn’t look very different. In fact, the only difference here is that
we use the JPAQuery
as the base, whereas the
former example used the collection wrapper. So you probably won’t be too
surprised to see that there’s not much difference in implementing this
scenario for a MongoDB store (Example 3-10).
Example 3-10. Using Querydsl MongoDB module with Spring Data MongoDB
MongoOperations
operations
=
…
// obtain MongoOperations
MongodbQuery
query
=
new
SpringDataMongodbQuery
(
operations
,
Product
.
class
);
QProduct
$
=
QProduct
.
product
;
List
<
Product
>
result
=
query
.
where
(
$
.
description
.
contains
(
"Apple"
).
list
();
assertThat
(
result
,
hasSize
(
3
));
assertThat
(
result
,
hasItems
(
macBook
,
iPad
,
iPod
));
As you just saw, the execution of queries with Querydsl generally consists of three major steps:
Setting up a store-specific query instance
Applying a set of filter predicates to it
Executing the query instance, potentially applying projections to it
Two of these steps can be considered boilerplate, as they will usually result in very similar code being written. On the other hand, the Spring Data repository tries to help users reduce the amount of unnecessary code; thus, it makes sense to integrate the repository extraction with Querydsl.
The core of the integration is the
QueryDslPredicateExecutor
interface ,which specifies the API that clients can use to execute
Querydsl predicates in the flavor of the CRUD methods provided through
CrudRepository
. See Example 3-11.
Currently, Spring Data JPA and MongoDB
support this API by providing implementation classes implementing the
QueryDslPredicateExecutor interface shown in Example 3-11. To expose this API through your
repository interfaces, let it extend
QueryDslPredicateExecutor
in addition to
Repository
or any of the other available base interfaces (see Example 3-12).
Extending the interface will have two
important results: the first—and probably most obvious—is that it pulls
in the API and thus exposes it to clients of
CustomerRepository
. Second, the Spring
Data repository infrastructure will inspect each repository interface
found to determine whether it extends
QueryDslPredicateExecutor
. If it does and
Querydsl is present on the classpath, Spring Data will select a special
base class to back the repository proxy that generically implements the
API methods by creating a store-specific query instance, bind the given
predicates, potentially apply pagination, and eventually execute the
query.
The approach we have just seen solves the problem of generically executing queries for the domain class managed by the repository. However, you cannot execute updates or deletes through this mechanism or manipulate the store-specific query instance. This is actually a scenario that plays nicely into the feature of repository abstraction, which allows you to selectively implement methods that need hand-crafted code (see Manually Implementing Repository Methods for general details on that topic). To ease the implementation of a custom repository extension, we provide store-specific base classes. For details on that, check out the sections Repository Querydsl Integration and Mongo Querydsl Integration.
3.149.236.27