Advanced Criteria Queries and Specifications in Spring Boot JPA

In this blog post, we will explore the advanced features of `Criteria Queries` and `Specifications` in Spring Boot JPA. We will demonstrate how to build complex, type-safe queries for managing our application.

Introduction to Criteria Queries and Specifications

Criteria Queries and Specifications are powerful tools in Spring Data JPA for building dynamic, type-safe queries. Criteria Queries offer a flexible, programmatic way to construct queries, while Specifications provide reusable, composable query components

Setting up Criteria Queries in Spring Boot JPA

To start using Criteria Queries in your Spring Boot application, you need to have the following dependencies in your pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Make sure you have a data source configured in application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true

Creating Entities and Repositories

Let’s start by creating a simple User entity and a corresponding UserRepository

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    private String email;

    // Getters and Setters
}

UserRepository

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Building Criteria Queries

Criteria Queries are created using CriteriaBuilder and CriteriaQuery APIs provided by JPA. Here’s a step-by-step guide to building and executing Criteria Queries.

Basic Criteria Query Example

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.List;

@Service
public class UserService {

    @Autowired
    private EntityManager entityManager;

    public List<User> getUsersByName(String name) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);
        query.select(root).where(cb.equal(root.get("name"), name));

        return entityManager.createQuery(query).getResultList();
    }
}

In this example:

  • CriteriaBuilder is used to construct the CriteriaQuery
  • CriteriaQuery represents the query itself.
  • Root represent the entity we are querying against
  • where() add condition to the query

Dynamic Queries

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

@Service
public class UserService {

    @Autowired
    private EntityManager entityManager;

    public List<User> getUsers(String name, String email) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);

        List<Predicate> predicates = new ArrayList<>();
        if (name != null) {
            predicates.add(cb.equal(root.get("name"), name));
        }
        if (email != null) {
            predicates.add(cb.equal(root.get("email"), email));
        }

        query.select(root).where(cb.and(predicates.toArray(new Predicate[0])));

        return entityManager.createQuery(query).getResultList();
    }
}

Sorting and Pagination

We also can add sorting and pagination to your Criteria Queries:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import java.util.List;

@Service
public class UserService {

    @Autowired
    private EntityManager entityManager;

    public List<User> getUsersSortedByName(int pageNumber, int pageSize) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);

        query.select(root).orderBy(cb.asc(root.get("name")));

        return entityManager.createQuery(query)
                .setFirstResult(pageNumber * pageSize)
                .setMaxResults(pageSize)
                .getResultList();
    }
}

In this example orderBy is used to sort results, and setFirstResult() and setMaxResult() is used for pagination.

Joins and Fetching Related Data

We can also joins to fetch related data

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Root;
import java.util.List;

@Service
public class UserService {

    @Autowired
    private EntityManager entityManager;

    public List<User> getUsersWithOrders() {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> userRoot = query.from(User.class);
        Join<User, Order> orderJoin = userRoot.join("orders");  // Assuming User has a collection of Order entities

        query.select(userRoot).distinct(true);  // Avoid duplicate users if they have multiple orders

        return entityManager.createQuery(query).getResultList();
    }
}

Advance Criteria Queries and Specifications

Suppose you need to find users based on a combination of criteria, such as name, email, birthdate range, and department.

UserSpecification.java

import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.time.LocalDate;

public class UserSpecifications {

    public static Specification<User> hasName(String name) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) ->
            cb.equal(root.get("name"), name);
    }

    public static Specification<User> hasEmail(String email) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) ->
            cb.equal(root.get("email"), email);
    }

    public static Specification<User> birthDateBetween(LocalDate startDate, LocalDate endDate) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) ->
            cb.between(root.get("birthDate"), startDate, endDate);
    }

    public static Specification<User> isActive(Boolean isActive) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) ->
            cb.equal(root.get("active"), isActive);
    }

    public static Specification<User> belongsToDepartment(String department) {
        return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) ->
            cb.equal(root.get("department"), department);
    }
}

UserService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List<User> findUsers(String name, String email, LocalDate startDate, LocalDate endDate, String department, Boolean isActive) {
        Specification<User> spec = Specification.where(null);

        if (name != null) {
            spec = spec.and(UserSpecifications.hasName(name));
        }
        if (email != null) {
            spec = spec.and(UserSpecifications.hasEmail(email));
        }
        if (startDate != null && endDate != null) {
            spec = spec.and(UserSpecifications.birthDateBetween(startDate, endDate));
        }
        if (department != null) {
            spec = spec.and(UserSpecifications.belongsToDepartment(department));
        }
        if (isActive != null) {
            spec = spec.and(UserSpecifications.isActive(isActive));
        }

        return userRepository.findAll(spec);
    }
}

In this example, we build a dynamic query based on various criteria such as name, email, birthDate, department, and isActive. Each condition is added to the Specification if the corresponding parameter is not null

Combining Specifications with Advanced Queries

Here’s an example where we combine AND and OR conditions to create more sophisticated queries.

public List<User> findUsersByComplexCriteria(String name, String email, LocalDate startDate, LocalDate endDate, String department, Boolean isActive) {
    Specification<User> nameOrEmailSpec = Specification.where(UserSpecifications.hasName(name))
                                                       .or(UserSpecifications.hasEmail(email));

    Specification<User> combinedSpec = nameOrEmailSpec.and(UserSpecifications.birthDateBetween(startDate, endDate))
                                                      .and(UserSpecifications.belongsToDepartment(department))
                                                      .and(UserSpecifications.isActive(isActive));

    return userRepository.findAll(combinedSpec);
}

In this example, we create a query where a User must either have specified name or email and also match the birthDate range, department and isActive status.

Advance Sorting and Pagination

Combine sorting and pagination with Criteria Queries to manage large datasets:

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

public List<User> findUsersSortedByNameAndPaged(String name, int page, int size) {
    Specification<User> spec = UserSpecifications.hasName(name);
    Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Order.asc("name")));

    return userRepository.findAll(spec, pageable).getContent();
}

In this example, PageRequest.of(page, size, Sort.by(Sort.Order.asc(“name))) ise used to create a Pageable object with sorting and pagination

Best Practices for using Criteria Queries and Specifications

  • Keep Specification Simple: Write simple and reusable specifications
  • Reuse Specifications; Combine existing specification to avoid code duplication.
  • Optimize performance: Ensure your queries are optimized for performance, especially when dealing with large datasets.
  • user distinct Judiciously: Avoid using distinct unless necessary, as it can affect query performance.

Example Code: https://github.com/hongquan080799/CriteriaQueryExample

Conclusion

In this blog post, we’ve explored advanced techniques for using Criteria Queries and Specifications in Spring Boot JPA. We covered:

  • Basic concepts: Understanding Specification and Criteria Queries
  • Complex Queries: Combining multiple criteria and dynamic queries
  • Sorting and Pagination: Managing large datasets.
  • Fetching related data: Using joins in criteria queries
  • Best practices: Tips for writing efficient and maintainable queries

By mastering these techniques, you can build powerful and flexible data access layers for your Spring Boot applications. Experiment with these examples and adapt them to your specific requirements to fully harness the power of JPA Criteria Queries and Specification.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top