Searching and filtering using JPA specification

Dynamic Filtering with JPA Specification and Jakarta Criteria API

Searching and filtering using JPA specification

About JPA Specification :

Java Persistence API (JPA) Specification: The JPA specification outlines the standard programming interfaces, annotations, and behaviors that developers can rely on when using JPA to interact with relational databases. It defines how various JPA features and concepts should work, ensuring consistency and portability across different JPA implementations (e.g., Hibernate, EclipseLink).

The JPA specification includes details about entity mapping, relationships, querying (JPQL and Criteria API), transactions, caching, concurrency control, and more. It serves as a guide for both developers and JPA providers to implement and use JPA in a standardized way.

Configuration for the project :

Click here for the spring initializer

  1. Then download the project below dependencies

  2. I'm using IntelliJ IDEA as IDE You can you any other as well, After downloading this project just open it with any IDE

  3. Then do the DataBase configuration

  4. Then I'm using this entity for Searching and filtering

  5. Then create a repository for this entity

     @Repository
     public interface UserDetailsRepository extends JpaRepository<UserDetails,Long>, JpaSpecificationExecutor<UserDetails> {
    
     }
    
  6. Then created a class for Search criteria

     public class SearchCriteria {
    
         private String key;   //column name
         private String operation;  // what will be the operation like equal and like or so on
         private Object value;   // and this is the value we will look for in DB
     }
    
  7. Another class for JpaSpecificationForUserDetails and passing list of criteria as a constructor so I can use it further and we need to override a method as well which name is toPredicate,

Here's what the method does in more detail:

  • toPredicate Method: This method is used to create a Predicate that represents a filter condition for querying entities based on specified criteria.

  • Parameters:

    • Root<UserDetails> root: Represents the root entity in the query, in this case, the UserDetails entity. It allows you to access attributes of the entity for filtering.

    • CriteriaQuery<?> query: Represents the overall query being constructed. It's used to specify the query's structure and other information.

    • CriteriaBuilder criteriaBuilder: Provides methods for constructing various query elements like predicates, expressions, and more.

  • Predicate Construction: Inside the toPredicate method, you're creating a list of individual Predicate conditions that specify how you want to filter the query results. These conditions are combined using logical operators to create a single filter condition.

  • Return Value: The method returns a single Predicate that represents the combined filter condition. This combined condition can then be used as a filter in a JPA query to retrieve the desired results.

Overall, the toPredicate method allows you to build complex filtering conditions using the JPA Criteria API. You construct predicates based on various criteria and combine them to create a meaningful filter that's applied when querying your entity. This approach provides a programmatic way to create dynamic and structured queries in a type-safe manner.

I hard-coded values but you can make it dynamic You can take data from the criteria list we have both filed in criteria we just need to pass dynamically.

package com.jpa.searchingfiltring.specification;

import com.jpa.searchingfiltring.entity.UserDetails;
import com.jpa.searchingfiltring.repository.UserDetailsRepository;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Predicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import java.util.ArrayList;
import java.util.List;



public class JpaSpecificationForUserDetails implements Specification<UserDetails> {

    @Autowired
    UserDetailsRepository hotelsRepository;

    private List<SearchCriteria> criteriaList;

    public JpaSpecificationForUserDetails(List<SearchCriteria> criteriaList) {
        super();
        this.criteriaList = criteriaList;

    }


    @Override
    public Predicate toPredicate(Root<UserDetails> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

        List<Predicate> predicateList =  new ArrayList<>();

        predicateList.add(criteriaBuilder.equal(root.<Integer> get("id"),1));

        predicateList.add(criteriaBuilder.greaterThanOrEqualTo(root.<String> get("address"), "kolkata"));
//
        predicateList.add(criteriaBuilder.notEqual(root.<Boolean> get("city"),"kashipur"));
////
        predicateList.add(criteriaBuilder.like(criteriaBuilder.lower(root.<String>get("last_name"))  , "%" + "sha" + "%"));

        return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        // this line is taking one by one and adding 1 by 1 with and
    }
}
  1. Then in service, we will create a new instance of this class like this and pass criteriaList, and then we pass that instance in userDetailsepository with the findAll method by Jpa and then just simply hit that method you will give responses according to your predicate.

      public void findByHardCodedValue() {
    
             //we need to crate a list of criteria like this and just pass in jpaSpecificationForUserDetails,
             // so we can access form there
             List<SearchCriteria> criteriaList = new ArrayList<>();
    
             JpaSpecificationForUserDetails jpaSpecificationForUserDetails = new JpaSpecificationForUserDetails(criteriaList);
    
             List<UserDetails> response = userDetailsRepository.findAll(jpaSpecificationForUserDetails);
    
             System.out.println("response+++++++" + response);
         }
    

Conclusion: The amalgamation of the Jakarta Criteria API and the JPA Specification bestows developers with an arsenal to conjure queries that are as dynamic as they are robust. This synergy propels the domain of Jakarta EE applications toward flexible and effective database interactions that resonate with the evolving demands of modern software development.

In summary, dynamic filtering with the JPA Specification and Jakarta Criteria API empowers developers to compose queries that seamlessly adapt to various scenarios. By leveraging these tools, developers can usher in a new era of efficient and adaptive database interactions.