The third part of my Spring Data JPA tutorial described how you can create custom queries by using query methods. This blog entry will describe how you can implement more advanced queries by using the JPA criteria API.
If you have read the previous part of this tutorial, you might remember that the search function which I used as an example returned all persons whose last name matched with the given search criteria. This requirement is now replaced with a new one:
- The search function must return only such persons whose last name begins with the given search term.
I am going to walk you through the implementation of this requirement next.
Required Steps
The steps required to implement the new search function are following:
- Creating the JPA criteria query.
- Extending the repository to support JPA criteria queries.
- Using the created criteria query and repository.
Each of these steps is described with more details in following.
Building the JPA Criteria Query
Spring Data JPA uses the specification pattern for providing an API which is used to create queries with the JPA criteria API. The heart of this API is the Specification interface which contains a single method called toPredicate(). In order to build the required criteria query, you must create a new implementation of the Specification interface and build the predicate in the toPredicate() method.
Before going in to the details, I will introduce the source code of my static metal model class which is used to create type safe queries with the JPA criteria API. The source code of the Person_ class is given in following:
* A meta model class used to create type safe queries from person
* information.
* @author Petri Kainulainen
*/
@StaticMetamodel(Person.class)
public class Person_ {
public static volatile SingularAttribute<Person, String> lastName;
}
A clean way to create specifications is to implement a specification builder class and use static methods to build the actual specification instances. My specification builder class is called PersonSpecifications and its source code given in following:
* A class which is used to create Specification objects which are used
* to create JPA criteria queries for person information.
* @author Petri Kainulainen
*/
public class PersonSpecifications {
/**
* Creates a specification used to find persons whose last name begins with
* the given search term. This search is case insensitive.
* @param searchTerm
* @return
*/
public static Specification<Person> lastNameIsLike(final String searchTerm) {
return new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> personRoot, CriteriaQuery<?> query, CriteriaBuilder cb) {
String likePattern = getLikePattern(searchTerm);
return cb.like(cb.lower(personRoot.<String>get(Person_.lastName)), likePattern);
}
private String getLikePattern(final String searchTerm) {
StringBuilder pattern = new StringBuilder();
pattern.append(searchTerm.toLowerCase());
pattern.append("%");
return pattern.toString();
}
};
}
}
Testing my specification builder implementation is pretty straightforward. I used Mockito mocking framework to mock the JPA criteria API. The source code of my test class is given following:
* @author Petri Kainulainen
*/
public class PersonSpecificationsTest {
private static final String SEARCH_TERM = "Foo";
private static final String SEARCH_TERM_LIKE_PATTERN = "foo%";
private CriteriaBuilder criteriaBuilderMock;
private CriteriaQuery criteriaQueryMock;
private Root<Person> personRootMock;
@Before
public void setUp() {
criteriaBuilderMock = mock(CriteriaBuilder.class);
criteriaQueryMock = mock(CriteriaQuery.class);
personRootMock = mock(Root.class);
}
@Test
public void lastNameIsLike() {
Path lastNamePathMock = mock(Path.class);
when(personRootMock.get(Person_.lastName)).thenReturn(lastNamePathMock);
Expression lastNameToLowerExpressionMock = mock(Expression.class);
when(criteriaBuilderMock.lower(lastNamePathMock)).thenReturn(lastNameToLowerExpressionMock);
Predicate lastNameIsLikePredicateMock = mock(Predicate.class);
when(criteriaBuilderMock.like(lastNameToLowerExpressionMock, SEARCH_TERM_LIKE_PATTERN)).thenReturn(lastNameIsLikePredicateMock);
Specification<Person> actual = PersonSpecifications.lastNameIsLike(SEARCH_TERM);
Predicate actualPredicate = actual.toPredicate(personRootMock, criteriaQueryMock, criteriaBuilderMock);
verify(personRootMock, times(1)).get(Person_.lastName);
verifyNoMoreInteractions(personRootMock);
verify(criteriaBuilderMock, times(1)).lower(lastNamePathMock);
verify(criteriaBuilderMock, times(1)).like(lastNameToLowerExpressionMock, SEARCH_TERM_LIKE_PATTERN);
verifyNoMoreInteractions(criteriaBuilderMock);
verifyZeroInteractions(criteriaQueryMock, lastNamePathMock, lastNameIsLikePredicateMock);
assertEquals(lastNameIsLikePredicateMock, actualPredicate);
}
}
However, if you have to build more complex queries by using this approach, testing your queries will become more troublesome because the JPA criteria API is not the easiest one to mock. In this case it is a good idea to divide the search conditions into multiple specifications and use the Specifications class to combine your specification instances. This way your unit tests don’t become so complex but you can still harness the power of the JPA criteria API in your application.
Extending the Repository
Extending your Spring Data JPA repository to support JPA criteria queries is quite easy. All you have to do is to extend the JpaSpecificationExecutor interface. This gives you access to the findAll(Specification spec) method which returns all entities fulfilling the search conditions specified by the specification. The source code of my PersonRepository is given in following:
* 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>, JpaSpecificationExecutor<Person> {
}
Using the Specification Builder and the Repository
The last step is to implement the service class which uses the created specification builder and the repository. The search() method of the PersonService interface takes the used search term as a parameter. The relevant part of the PersonService 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);
}
The implementation of the search() method is very simple. It simply passes the search term to the lastNameIsLike() method of the PersonSpecifications class and gives the created specification object to the PersonRepository. The source code of the implementation of the search() method is given in following:
* 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 PersonSpecifications class to the repository.
return personRepository.findAll(lastNameIsLike(searchTerm));
}
}
This solution is not perfect from the architectural point of view because it introduces a dependency between the service layer and the Spring Data JPA. A general guideline is that an upper layer should not have any knowledge about the implementation details of the layers located below it.
One solution to this problem would be to create a custom search method and integrate it with the generic repository abstraction as described in the reference manual of Spring Data JPA. However, this would mean that you would have to write a lot of boilerplate code which does not really add any value to your application. Also, since the goal of the Spring Data JPA is to reduce the amount of boilerplate code, I think that my solution is an acceptable compromise between over engineering and getting things done in a clean way.
Oh, this reminds me of something. We should not forget the unit test of the search() method. The source code of this unit test is given in following:
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(Specification.class))).thenReturn(expected);
List<Person> actual = personService.search(SEARCH_TERM);
verify(personRepositoryMock, times(1)).findAll(any(Specification.class));
verifyNoMoreInteractions(personRepositoryMock);
assertEquals(expected, actual);
}
}
What is Next?
I have now demonstrated to you how you can use the JPA criteria API with Spring Data JPA. As always, the example application of this blog entry is available at Github. The next part of my Spring Data JPA tutorial describes how you can use Querydsl for building queries with Spring Data JPA.



{ 32 comments… read them below or add one }
Your project structure is amazing. I was blown away by how well you laid it out. I am still trying to soak it all in and I’m going to keep poring over it to really understand it all. I expect that it will serve as a foundation for future Spring 3.1 JPA based projects. I can’t thank you enough for putting together such a wonderful tutorial where you took the time to set up an elegant foundation.
Stone,
thanks for your comment. It is always nice to hear that I could actually help someone to learn something new. Also, it would be nice to hear which things are hard to understand so that I could try to provide a bit better explanation.
It took some time for me to grasp how the Specification stuff worked, I still don’t think I’m very clear on that. It also took a little bit of time to understand how the environment was getting initialized in the ApplicationContext (I’m still a bit of a novice when it comes to Spring configurations, and from what I’ve gathered, it seems that Spring parsed the data from the @ImportResource and @PropertySource specifications to initialize the environment). One other issue that I had was figuring out how to access all of the pages and when it was deployed locally (I had to prefix all of the form:action and href values to include the project name prefix). Lastly, the verify statements in the test cases were also new to me, so I learned about Mockito from this project as well.
I’d like to give back to you — I found a few issues in the code that you may want to include. I kept getting a NPE in AbstractPathImpl.get() method. To get around it, I had to move the Person_ class into the same package as Person (~.model). I also changed the return statement on the PersonRepositoryService.update() method to “return personRepository.save(person);
” instead of “return person;” — the value was never getting updated in the database. This necessitated changing the PersonRepositoryServiceTest.update() method to:
PersonDTO updated = PersonTestUtil.createDTO(PERSON_ID, FIRST_NAME_UPDATED, LAST_NAME_UPDATED);
Person person = PersonTestUtil.createModelObject(PERSON_ID, FIRST_NAME, LAST_NAME);
when(personRepositoryMock.findOne(updated.getId())).thenReturn(person);
when(personRepositoryMock.save(person)).thenReturn(person);
Person returned = personService.update(updated);
verify(personRepositoryMock, times(1)).findOne(updated.getId());
verify(personRepositoryMock, times(1)).save(person);
verifyNoMoreInteractions(personRepositoryMock);
assertPerson(updated, returned);
Finally, there was a simple type in the PersonRepositoryServiceTest.assertPerson() — the last assert statement should read “assertEquals(expected.getLastName(), actual.getLastName());”.
Again, thank you so much for such a thoughtful and well designed tutorial — I learned a lot.
Stone,
thanks your comment. I am planning to add more links to resources which contains tutorials and other material about the used libraries and frameworks. I will also check out the issues you mentioned later today. By the way, did you use the H2 in memory database when you noticed these problems? In any case, thanks for your contribution. :)
I didn’t use H2, I used MySQL.
Stone,
I tried to reproduce the problem you were having with the update() method of Person RepositoryPersonService class by using MySQL 5.5.19. Unfortunately I was not able to reproduce it. In my environment the updates made to the Person instance were updated to the database.
The thing is that you should not have to call the save() method of PersonRepository when you are updating the information of a person. The reason for this is that Hibernate will automatically detect the changes made to persistent objects during the transaction and synchronize the changes with the database after the transaction is committed.
Check the Working with Objects Section of the Hibernate reference manual for more details: http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/objectstate.html#objectstate-overview
Common causes for the problem you were having are:
You do not have got transaction at all (the @Transactional annotation is not used either at method or class level)
The transaction is read only (The readOnly property of the @Transactional annotation is set to true)
The state of the updated entity is not persistent (Check the link to the reference manual for more details).
I am wondering if this advice helped you? (I am bit of a perfectionist so it would be a personal victory for me to help you to remove that unnecessary call to the save() method of PersonRepository).
Hi Petri,
Thank you for the very nice explanation. For Spring Data JPA + criteria queries, is this the only signature available ?
List repository.findAll(Specification s);
If I know that my query will return only a single result, can I use something like
T repository.find(Specification s);
I tried find(), but I exceptions, e.g. “No property find found for type class domain.Customer”.
So, is findAll() the only available query method with the Specification parameter?
Thanks,
David
Hi David,
You can use the findOne(Specification<T> spec) method of the JpaSpecificationExecutor interface to get a single entity which matches with the given specification.
See the API for more details: http://static.springsource.org/spring-data/data-jpa/docs/1.0.x/api/org/springframework/data/jpa/repository/JpaSpecificationExecutor.html
I hope that this was helpful.
Hi Petri,
Nice tutorial with good and clear examples which gives good insight on Spring Data JPA. Thanks for that.
I tried to implement jpa criteria and got NPE on org.hibernate.ejb.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:141)
Apparently, I got exact same exception, when i tried to run your project – tutorial 4.
Then I moved my staticMetamodel to the package where my entity is and this exception went away. But the simple criteria is also not returning anything. I did check the table and can retrive data before I apply criteria to filter. So I am stumped. any clues ?
Hi Amol,
Thanks for your comment. I finally ended up moving the static meta model class to the same package where the Person entity is located. Hopefully this will finally fix the issue with the NPE you (and Stone) mentioned. Thanks for the bug report. I should have done this ages ago but somehow I managed to forget this issue.
In my experience, if a query is not returning the correct results, the problem is almost always in the created criteria. It would be helpful if you could give a bit more detailed description about your problem. The answers to following questions would help me to get a better idea of the situation:
Well, I managed to fix that. It was with the created criteria as you rightly said.
Thanks again.
Hi Amol,
great to hear that you managed to solve your problem.
Hei Petri of all the tutorials about jpa I found yours has been the most helpful! But I have still a doubt, we will see if you can find a solution: If I want to create an specification of one object that it is a parameter in another object how can I do it? for example: imagine that your object person has another attribute that is adress, and Adress has as attributes street and number, how can I create an specification that obtain all the people that live in one street?
Thanks in advance!!!
Hi Albert,
Thanks for your comment. It was nice to see that that you enjoyed my tutorials. The answer to your question is: it can be done. I am currently at work but I will describe the solution after I get back to home.
Hi Petri,
I am also trying to implement similar criteria. Hoping to see some input from you.
Many Thanks,
Hi Albert,
Lets assume that you have got a Person object which has a reference to a Address object. So, the source code of the Person class could be something like this:
@Entity
@Table("persons")
public class Person {
private Address address;
public Person() {
}
public Address getAddress() {
return address;
}
}
Now, the source code of the Address class is following:
@Embeddable
public class Address {
private String street;
private int number;
public Address() {
}
public String getStreet() {
return street;
}
public int getNumber() {
return number;
}
}
As you said, you want search all persons which are living in the same street. This criteria is built like this (Note that I am not using the static meta model in this example):
public class PersonSpecifications {
public static Specification livesInGivenStreet(final String street) {
return new Specification() {
personRoot, CriteriaQuery< ?> query, CriteriaBuilder cb) {get("street"), street);
@Override
public Predicate toPredicate(Root
return cb.equal(root.
get("address").
}
};
}
}
In this solution I have assumed the the database column containing the street in which the person lives is found from persons database table. Is the case for you or are you using a separate entity object in your domain model instead of component?
this is exactly what I was looking for. I was having problems in this line:
“root.get(“address”).get(“street”), street);”
I didn’t know how to reach the street from address, I thought I had to make an “innerjoin” but I have seen that if I execute your code the innerjoin is created alone when the query is created.
Thanks a lot for your help!!!! I’ll try now to make it a little more complicate using the metamodel and using classes than extend from other classes, we will see if it works fine…thanks again.
Albert,
Good to see that I could help you out. If you need more information about the the JPA Criteria API, you should check out this article:
http://www.ibm.com/developerworks/java/library/j-typesafejpa/
Hi Petri,
I have 3 tables as Check, User and UserDetail
Check – main search table has
userid
and other fields
User table has
userid
and other fields
UserDetail table has
userid
firstname
lastname
the domain model is
Check class
has User
User class
has userDetail
I am trying to build predicate to perform search on firstname and that is giving me trouble.
my predicate is as below
predicate = cb.equal(root.get(“user”).get(“userid”).get(“userDetail”).get(“firstname”), searchName)
this throws exception as Illegal attempt to dereference path source [null,user]
Any clues on how to build the search with these 3 tables ? Do i have to use some Join while building predicate ?
If I create a link between Check and UserDetail table by adding userdetail in Check then following works fine
predicate = cb.equal(root.get(“userDetail”).get(“firstname”), searchName)
Thanks in advance
Hi Amol,
If I understood your domain model correctly, you can obtain the correct predicate with this code:
cb.equal(root.<User>get(“user”).<UserDetail>get(“userDetail”).<String>get(“firstname”), searchName);
Thanks for the reply Petri but that throws exception as “Unable to resolve attribute [userDetail] against path”. ?
Hi Amol,
it seems that I would have to see the source code in order to help you. It seems that the attribute called userDetail cannot be resolved. This means that the property called userDetail is not found.
This seems a bit puzzling because I assumed that the Check class contains a property called user, the User class contains a property called userDetail and the UserDetail class contains property firstName. Are you trying to navigate from Check to UserDetail when building the Predicate?
Hi Petri,
Here is the code snippet. I have removed unwanted comments, fields and getter/setter methods.
You are right CheckRecord has User has userDetail has firstName.
@PersistenceUnit(name = "core-dal")public class CheckRecord {
private Long id;
private String status;
private Date expiry;
private User user;
@ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = User.class)
@JoinColumn(name = "userId")
public User getUser() {
return user;
}
}
@Entity
@Table(name = "UserTable")
@PersistenceUnit(name = "core-dal")
public class User {
private Long id;
private String username;
private Account account;
private UserDetail userDetail;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
public UserDetail getUserDetail() {
return userDetail;
}
}
@Entity
@Table(name = "UserDetail")
@PersistenceUnit(name = "core-dal")
public class UserDetail {
private Long id;
private String firstName;
private String lastName;
private User user;
public String getFirstName() {
return firstName;
}
}
Note: Added code tags and removed some unnecessary setters – Petri
Hi Amol,
I noticed that the getUser() method of UserDetail class is missing. Does it look like this:
@OneToOne
@JoinColumn("userId")
public User getUser() {
return user;
}
Hi Petri,
yes it is like that. I removed that and others so my post is not too big.
In this case the following specification builder should work:
public class CheckRecordSpecifications {
public static Specification<CheckRecord> firstNameIs(final String searchTerm) {
return new Specification<CheckRecord>() {
@Override
public Predicate toPredicate(Root<CheckRecord> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.<User>get(“user”).<UserDetail>get(“userDetail”).<String>get(“firstName”), searchTerm);
}
};
}
}
spot on.. that did work.. I think last night eclipse was culprit as it was not picking up the latest class file.
Many Thanks for your help.
Amol,
Great!
Hi Petri!
I have been working in this issue last week, but when I thought it was working well suddenly this problem has appeared: “org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Illegal attempt to dereference path source [null];”
What I´m doing is this:
Path path = root.get(CustomsOrder_.messageType);
predicates.add(cb.equal(path.get(MessageType_.group), messageGroup));
My CustomsOrder has a MessageType, this type belongs to a group and I would like to find a CustomsOrder by the group.
Do u have an idea what can happen here?
Tahnks in advance
Hi Albert,
the exception you mentioned is thrown when the path used to get the compared property is not correct. You should definitely try to create the predicate without using the static meta model. Does the following work or is the same exception thrown?
predicates.add(cb.equal(root.get("messageType").get("group"), messageGroup));
Also, are you saying that CustomsOrder class has a property called messageType, and MessageType has a property called group?
Hi Petri!
I found the problem, after some hours checking the solution I have discovered that MessageType is an enumerator that is grouped by another enumerator that it is MessageGroup, as I didn’t do this code I assumed both were regular classes. So when I was getting the MessageType I could not reach the MessageGroup.My finall solution is to obtain from MessageType all the messages that belong to a group and search by list of messages instead of group. If you think that another solution more elegant exists please make me know it.
Code I have used:
List list =getMessagesTypeByGroup(group); //obtain msg by group selected
predicates.add(root.get(CustomsOrder_.messageType).in(list));
Thanks for your replying.
Hi Albert,
Good to see that you were able to solve your problem.