The best way to use the Spring Data JPA Specification
source link: https://vladmihalcea.com/spring-data-jpa-specification/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
The best way to use the Spring Data JPA Specification
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
Introduction
In this article, we are going to what is the best way to use the Spring Data JPA Specification when combining multiple predicates with the result set ordering logic.
While you can also use query methods or the @Query
annotation to define your Spring Data queries, the Spring Data JPA Specification allows you to compose dynamically various filtering criteria, which may be very convenient when you’d, otherwise, end up with lots of query method permutations.
Domain Model and Repository
Let’s assume we are using the following PostComment
entity in our application:
For this entity, we create the PostCommentRepository
that extends two interfaces:
- the
BaseJpaRepository
from the Hypersistence Utils project, which is a better alternative to the defaultJpaRepository
from Spring Data - the
JpaSpecificationExecutor
, which provides the Spring DataSpecification
filtering methods
Repository public interface PostCommentRepository extends BaseJpaRepository<PostComment, Long>, JpaSpecificationExecutor<PostComment> { interface Specs { static Specification<PostComment> byPost(Post post) { return (root, query, builder) -> builder.equal(root.get(PostComment_.post), post); } static Specification<PostComment> byStatus(PostComment.Status status) { return (root, query, builder) -> builder.equal(root.get(PostComment_.status), status); } static Specification<PostComment> byReviewLike(String reviewPattern) { return (root, query, builder) -> builder.like(root.get(PostComment_.review), reviewPattern); } static Specification<PostComment> byVotesGreaterThanEqual( int votes) { return (root, query, builder) -> builder.greaterThanOrEqualTo(root.get(PostComment_.votes), votes); } static Specification<PostComment> orderByCreatedOn( Specification<PostComment> spec) { return (root, query, builder) -> { query.orderBy(builder.asc(root.get(PostComment_.createdOn))); return spec.toPredicate(root, query, builder); }; } } } |
Notice that we have a Specs
interface in which we define several Specification
definitions:
The byPost
Specification
filters the PostComment
entities that match the provided Post
entity reference:
static Specification<PostComment> byPost(Post post) { return (root, query, builder) -> builder.equal(root.get(PostComment_.POST), post); } |
Notice that we are using the
PostComment_
JPA Metamodel to reference the entity properties we are matching.For more details about the best way to generate and use the JPA Metamodel, check out this article.
The byStatus
Specification
filters the PostComment
entities that have the provided Status
:
static Specification<PostComment> byStatus(PostComment.Status status) { return (root, query, builder) -> builder.equal(root.get(PostComment_.status), status); } |
The byReviewLike
Specification
filters the PostComment
entities that have the review
property like the provided pattern:
static Specification<PostComment> byReviewLike(String reviewPattern) { return (root, query, builder) -> builder.like(root.get(PostComment_.review), reviewPattern); } |
The byVotesGreaterThanEqual
Specification
filters the PostComment
entities that have the votes
property greater than or equal to the provided vote count:
static Specification<PostComment> byVotesGreaterThanEqual( int votes) { return (root, query, builder) -> builder.greaterThanOrEqualTo(root.get(PostComment_.votes), votes); } |
The orderByCreatedOn
Specification
shows you how you can customize the Criteria API query that will be executed by Spring Data JPA. In this case, we are passing an existing Specification
, and we apply the ORDER BY logic to the existing query:
static Specification<PostComment> orderByCreatedOn( Specification<PostComment> spec) { return (root, query, builder) -> { query.orderBy(builder.asc(root.get(PostComment_.createdOn))); return spec.toPredicate(root, query, builder); }; } |
Testing Time
To find all the PostComment
entities that belong to a given parent Post
entity, we can use the byPost
Specification
:
List<PostComment> comments = postCommentRepository.findAll( byPost(post) ); |
And Hibernate is going to execute the following SQL query:
SELECT p1_0.id, p1_0.created_on, p1_0.parent_id, p1_0.post_id, p1_0.review, p1_0.status, p1_0.votes FROM post_comment p1_0 WHERE p1_0.post_id = 1 |
Adding an ORDER BY clause to the Spring Data JPA Specification
If we want to add an ORDER BY clause to the previous SQL query, we can do it like this:
List<PostComment> comments = postCommentRepository.findAll( orderByCreatedOn( byPost(post) ) ); |
Notice that we passed the byPost
Specification
to the orderByCreatedOn
so that when Spring Data JPA runs the query, we get the chance to add the ORDER BY logic, as illustrated by the executed SQL query:
SELECT p1_0.id, p1_0.created_on, p1_0.parent_id, p1_0.post_id, p1_0.review, p1_0.status, p1_0.votes FROM post_comment p1_0 WHERE p1_0.post_id = 1 ORDER BY p1_0.created_on ASC |
Combining multiple Spring Data JPA Specifications
The greatest benefit of the Spring Data JPA Specification is that we can combine as many Spring Data JPA Specifications as our business use case demands.
For instance, if we want to get all PostComment
entities matching a given Post
, with Pending
status, that have a given review
pattern and with a number of votes greater than or equal to the provided threshold, we can write the query as follows:
List<PostComment> comments = postCommentRepository.findAll( orderByCreatedOn( byPost(post) . and (byStatus(PostComment.Status.PENDING)) . and (byReviewLike(reviewPattern)) . and (byVotesGreaterThanEqual(minVotes)) ) ); |
And Hibernate is going to generate the following SQL query:
SELECT p1_0.id, p1_0.created_on, p1_0.parent_id, p1_0.post_id, p1_0.review, p1_0.status, p1_0.votes FROM post_comment p1_0 WHERE p1_0.post_id = 1 AND p1_0.status = 0 AND p1_0.review like 'Awesome%' ESCAPE '' AND p1_0.votes >= 1 ORDER BY p1_0.created_on ASC |
Cool, right?
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
And there is more!
You can earn a significant passive income stream from promoting all these amazing products that I have been creating.
If you're interested in supplementing your income, then join my affiliate program.
Conclusion
The Spring Data JPA Specification is a very handy feature that allows you to compose your queries from multiple individual filtering options.
More, the query can be built dynamically at runtime based on the user input. And, as illustrated by the orderByCreatedOn
example, you are not limited to defining the WHERE clause filtering criteria since you can customize the entire Criteria API query that will be executed by Spring Data JPA.
2 Comments on “The best way to use the Spring Data JPA Specification”
-
Robert Gagnon
March 28, 2023is it possible to use “in” command with specification?
-
Via the
toPredicate
method, you can get access to theCriteriaQuery
and customize it any way you want.
-
Leave a Reply Cancel reply
Your email address will not be published. Required fields are marked *
Comment *
Before posting the comment, please take the time to read the FAQ page
Name *
Email *
Website
Notify me of follow-up comments by email.
This site uses Akismet to reduce spam. Learn how your comment data is processed.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK