Spring Data JPA repository patterns

David Kumar Jan 2026
2 tabs
package com.example.demo.repository;

import com.example.demo.model.User;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class UserSpecifications {

    public static Specification<User> hasName(String name) {
        return (root, query, cb) ->
            name == null ? null : cb.like(cb.lower(root.get("name")),
                "%" + name.toLowerCase() + "%");
    }

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

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

    public static Specification<User> createdBetween(
        LocalDateTime start,
        LocalDateTime end
    ) {
        return (root, query, cb) -> {
            if (start == null && end == null) return null;
            if (start == null) return cb.lessThanOrEqualTo(root.get("createdAt"), end);
            if (end == null) return cb.greaterThanOrEqualTo(root.get("createdAt"), start);
            return cb.between(root.get("createdAt"), start, end);
        };
    }

    public static Specification<User> buildDynamicQuery(
        String name,
        String email,
        Boolean active,
        LocalDateTime startDate,
        LocalDateTime endDate
    ) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            if (name != null && !name.isEmpty()) {
                predicates.add(cb.like(cb.lower(root.get("name")),
                    "%" + name.toLowerCase() + "%"));
            }

            if (email != null && !email.isEmpty()) {
                predicates.add(cb.equal(root.get("email"), email));
            }

            if (active != null) {
                predicates.add(cb.equal(root.get("active"), active));
            }

            if (startDate != null) {
                predicates.add(cb.greaterThanOrEqualTo(
                    root.get("createdAt"), startDate));
            }

            if (endDate != null) {
                predicates.add(cb.lessThanOrEqualTo(
                    root.get("createdAt"), endDate));
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }
}

// Usage:
// Page<User> results = userRepository.findAll(
//     Specification.where(UserSpecifications.hasName("John"))
//         .and(UserSpecifications.isActive(true)),
//     PageRequest.of(0, 20, Sort.by("createdAt").descending())
// );
2 files · java Explain with highlit

Spring Data JPA eliminates boilerplate DAO code with repository interfaces. I extend JpaRepository to get CRUD methods automatically—save, findById, findAll, delete. Custom query methods use method naming conventions—findByEmailAndActiveTrue generates queries from method names. @Query annotation defines JPQL or native SQL for complex queries. Pagination uses Pageable parameter and returns Page objects. Specifications enable dynamic queries with Criteria API. @EntityGraph optimizes fetching related entities, solving N+1 query problems. Projections return subsets of entity fields via interfaces. Transactions are managed declaratively with @Transactional. Auditing tracks creation and modification timestamps automatically. Spring Data JPA reduces database access code by 70% while maintaining flexibility for complex scenarios.