The fourth part of my Spring Data JPA tutorialdescribed how you can implement more advanced queries with the JPA criteria API. As you might remember, the goal of the previous part of this tutorial was to implement a search function which returns only such persons whose last name begins with the given search term. This blog entry will describe how you useQuerydsl and Spring Data JPA for the same purpose.
Lets get to work and see how you can fulfill this requirement.
Required Steps
The steps needed to implement the given requirement are following:
- Configuring the Maven integration of QueryDSL
- Generating the Querydsl query type
- Implementing the predicate builder class
- Extending the repository to support Querydsl predicates
- Using the created repository
Each of these steps is described with more details in following Sections.
Configuring the Maven Integration of Querydsl
The configuration of the maven integration of Querydsl consists two smaller phases:
First, you need to add the Querydsl dependencies to your pom.xml file. The needed dependencies are:
- Querydsl core which provides the core functions of Querydsl.
- Querydsl APT integration which provides support for the APT based code generation.
- Querydsl JPA which provides support for the JPA annotations.
The relevant part of the pom.xml is given in following:
<dependency>
<groupId>com.mysema.querydsl
</groupId>
<artifactId>querydsl-core
</artifactId>
<version>2.3.2
</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl
</groupId>
<artifactId>querydsl-apt
</artifactId>
<version>2.3.2
</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl
</groupId>
<artifactId>querydsl-jpa
</artifactId>
<version>2.3.2
</version>
</dependency>
Querydsl uses the Annotation Processing Tool of Java 6 for code generation. Thus, the next phase is to add the the configuration of the Maven APT plugin to the plugins section of yourpom.xml file. The relevant part of the pom.xml looks following:
<plugin>
<groupId>com.mysema.maven
</groupId>
<artifactId>maven-apt-plugin
</artifactId>
<version>1.0.2
</version>
<executions>
<execution>
<phase>generate-sources
</phase>
<goals>
<goal>process
</goal>
</goals>
<configuration>
<!-- Specifies the directory in which the query types are generated -->
<outputDirectory>target/generated-sources
</outputDirectory>
<!-- States that the APT code generator should look for JPA annotations -->
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor
</processor>
</configuration>
</execution>
</executions>
</plugin>
Generating the Querydsl Query Type
The next step is to generate the Querydsl query type which is used to construct queries with Querydsl. All you have to do is to build your project and the query type is generated under thetarget/generated-sources directory. If you open this directory, you should notice that the query type class is created in the same package than the Person class. Since the name of the model class is called Person, the name of the Querydsl query type is QPerson.
NOTE: Remember to add the target/generated-sources directory as a source directory for your project before moving forward (Check the documentation of your IDE for more details about this).
Implementing the Predicate Builder Class
In my previous part of this tutorial, I used a builder class with static methods to build the actualSpecification instances. I am following the same principle when building the Querydsl predicates by using the generated QPerson query type. The source code of my predicate builder class is given in following:
import
com.mysema.query.types.Predicate
;
import
net.petrikainulainen.spring.datajpa.model.QPerson
;
/**
* A class which is used to create Querydsl predicates.
* @author Petri Kainulainen
*/
public
class
PersonPredicates
{
public
static
Predicate lastNameIsLike
(
final
String
searchTerm
)
{
QPerson person
=
QPerson.
person
;
return
person.
lastName.
startsWithIgnoreCase
(searchTerm
)
;
}
}
At first I had no idea how my builder class could be tested, because the information about unit testing Querydsl is kind of hard to find. Luckily I was able to find a google groups thread, which discusses about this matter. After reading that thread I decided that testing the return value oftoString() method of the created predicate is enough because this scenario is quite simple. The source code of my unit test is given in following:
import
com.mysema.query.types.Predicate
;
import
org.junit.Test
;
public
class
PersonPredicatesTest
{
private
static
final
String
SEARCH_TERM
=
"Foo"
;
private
static
final
String
EXPECTED_PREDICATE_STRING
=
"startsWithIgnoreCase(person.lastName,Foo)"
;
@Test
public
void
lastNameLike
(
)
{
Predicate predicate
=
PersonPredicates.
lastNameIsLike
(SEARCH_TERM
)
;
String
predicateAsString
=
predicate.
toString
(
)
;
assertEquals
(EXPECTED_PREDICATE_STRING, predicateAsString
)
;
}
}
If you have read my blog entry about using the JPA criteria API with Spring Data JPA, you might remember that the unit test of my lastNameLike() method was rather long and looked a bit messy. The problem is that mocking the JPA criteria API becomes a lot more complex when you are building more complex queries. This means that writing pure unit tests for your code takes longer and longer.
My unit test for the Querydsl implementation of the same method is the exact opposite. It is short and looks quite clean. And more importantly, it is much faster to write. This means that you can concentrate on adding value to your application instead of verifying its behavior.
Extending the Repository to Support Querydsl Predicates
Extending the repository to support Querydsl predicates is quite straightforward process. All you have to do is to extend the QueryDslPredicateExecutor interface. This gives you access tofindAll(Predicate predicate) method. This returns all entities fulfilling the search conditions which are specified by the predicate. The source code of my PersonRepository interface is given in following:
import
org.springframework.data.jpa.repository.JpaRepository
;
import
org.springframework.data.querydsl.QueryDslPredicateExecutor
;
/**
* Specifies methods used to obtain and modify person related information
* which is stored in the database.
* @author Petri Kainulainen
*/
public
interface
PersonRepository
extends
JpaRepository
<Person, Long
>, QueryDslPredicateExecutor
<Person
>
{
}
Using the Created Repository
The last step is to implement the service class which uses the created predicate builder and the repository. The PersonService interface contains a method search(String searchTerm) which returns a list of persons matching with the given search term. The relevant part of thePersonService interface is given in following:
/**
* Declares methods used to obtain and modify person information.
* @author Petri Kainulainen
*/
public
interface
PersonService
{
/**
* Searches persons by using the given search term as a parameter.
* @param searchTerm
* @return A list of persons whose last name begins with the given search term. If no persons is found, this method
* returns an empty list. This search is case insensitive.
*/
public
List
<Person
>
search
(
String
searchTerm
)
;
}
Implementing the search method is pretty straightforward. My implementation uses thePersonPredicates class to obtain the Querydsl predicate and passes the received predicate forward to the PersonRepository. Since the findAll() method returns Iterable instead of List, I had to add an extra method which converts the returned Iterable into a List. The source code of thesearch(String searchTerm) method is given in following:
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
javax.annotation.Resource
;
import
static
net.
petrikainulainen.
spring.
datajpa.
repository.
PersonPredicates.
lastNameIsLike
;
/**
* This implementation of the PersonService interface communicates with
* the database by using a Spring Data JPA repository.
* @author Petri Kainulainen
*/
@Service
public
class
RepositoryPersonService
implements
PersonService
{
private
static
final
Logger LOGGER
=
LoggerFactory.
getLogger
(RepositoryPersonService.
class
)
;
@Resource
private
PersonRepository personRepository
;
@Transactional
(readOnly
=
true
)
@Override
public
List
<Person
>
search
(
String
searchTerm
)
{
LOGGER.
debug
(
"Searching persons with search term: "
+
searchTerm
)
;
//Passes the specification created by PersonPredicates class to the repository.
Iterable
<Person
>
persons
=
personRepository.
findAll
(lastNameIsLike
(searchTerm
)
)
;
return
constructList
(persons
)
;
}
private
List
<Person
>
constructList
(Iterable
<Person
>
persons
)
{
List
<Person
>
list
=
new
ArrayList
<Person
>
(
)
;
for
(Person person
:
persons
)
{
list.
add
(person
)
;
}
return
list
;
}
}
The same architectural remarks which I made in the previous part of my Spring Data JPA tutorialare also valid in this case. However, the end result looks pretty clean and simple. The only thing which is left, is to write a unit test for the search method. The source code of the unit test is given in following:
import
com.mysema.query.types.Predicate
;
import
org.junit.Before
;
import
org.junit.Test
;
import
static
junit.
framework.
Assert.
assertEquals
;
import
static
org.
mockito.
Mockito.
*;
public
class
RepositoryPersonServiceTest
{
private
static
final
String
SEARCH_TERM
=
"foo"
;
private
RepositoryPersonService personService
;
private
PersonRepository personRepositoryMock
;
@Before
public
void
setUp
(
)
{
personService
=
new
RepositoryPersonService
(
)
;
personRepositoryMock
=
mock
(PersonRepository.
class
)
;
personService.
setPersonRepository
(personRepositoryMock
)
;
}
@Test
public
void
search
(
)
{
List
<Person
>
expected
=
new
ArrayList
<Person
>
(
)
;
when
(personRepositoryMock.
findAll
(any
(Predicate.
class
)
)
).
thenReturn
(expected
)
;
List
<Person
>
actual
=
personService.
search
(SEARCH_TERM
)
;
verify
(personRepositoryMock, times
(
1
)
).
findAll
(any
(Predicate.
class
)
)
;
verifyNoMoreInteractions
(personRepositoryMock
)
;
assertEquals
(expected, actual
)
;
}
}
What is Next?
I have now demonstrated to you how you can build advanced queries with Spring Data JPA and Querydsl. As always, the example application described in this blog entry is available at Github. The next part of my tutorial describes the sorting capabilities of Spring Data JPA.
P.S. You might also want to learn how you can use Querydsl in a multi-module Maven project.