Spring Data JPA Specification With Pagination

The combination of Specification and Pageable in Spring Data JPA makes it super easy to fetch data with dynamic conditions and pagination. Let's walk through a step-by-step example using a Student entity.

1. Setting up the Project

Make sure you have the necessary dependencies in your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

2. Define the Entity

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String course;
    private Double grade;

    // getters, setters, etc.
}

3. Create the Repository:

Extend both JpaRepository and JpaSpecificationExecutor:

public interface StudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> {
}

4. Define Specifications

public class StudentSpecifications {
    
    public static Specification<Student> hasName(String name) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("name"), "%" + name + "%");
    }

    public static Specification<Student> inCourse(String course) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("course"), course);
    }

    public static Specification<Student> hasGradeAbove(Double grade) {
        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get("grade"), grade);
    }
}

5. Create a Service to Query with Pagination

@Service
public class StudentService {

    @Autowired
    private StudentRepository studentRepository;

    public Page<Student> findStudents(String name, String course, Double grade, Pageable pageable) {
        return studentRepository.findAll(
            Specification.where(StudentSpecifications.hasName(name))
                .and(StudentSpecifications.inCourse(course))
                .and(StudentSpecifications.hasGradeAbove(grade)),
            pageable
        );
    }
}

6. Using the Service:

You can use the PageRequest class to create an instance of Pageable:

@Autowired
private StudentService studentService;

public void fetchStudents() {
    Pageable firstPageWithTwoElements = PageRequest.of(0, 2);

    Page<Student> students = studentService.findStudents("John", "Mathematics", 80.0, firstPageWithTwoElements);
    
    // Access the list of students using students.getContent()
    for (Student student : students.getContent()) {
        // process each student
    }
}

7. Extending to Controllers

If you are integrating this into a web application, it's typical to pass pagination parameters like page, size, and sort from the client side:

@RestController
@RequestMapping("/students")
public class StudentController {

    @Autowired
    private StudentService studentService;

    @GetMapping
    public ResponseEntity<Page<Student>> getStudents(
            @RequestParam(required = false) String name,
            @RequestParam(required = false) String course,
            @RequestParam(required = false) Double grade,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id,asc") String[] sort) {

        List<Sort.Order> orders = new ArrayList<>();
        for (String sortOrder : sort) {
            String[] _sort = sortOrder.split(",");
            orders.add(new Sort.Order(_sort[1].equalsIgnoreCase("desc") ? Sort.Direction.DESC : Sort.Direction.ASC, _sort[0]));
        }
        
        Page<Student> students = studentService.findStudents(name, course, grade, PageRequest.of(page, size, Sort.by(orders)));
        return new ResponseEntity<>(students, HttpStatus.OK);
    }
}

That's it! With this setup, you can create dynamic queries based on filters (like name, course, and grade) and fetch results with pagination and sorting.

Comments