Query Methods
Create custom queries using method naming conventions, @Query annotation, and derived query methods
Derived Query Methods
Spring Data JPA can automatically generate queries based on method names. The framework parses the method name and creates the appropriate query. This is called query derivation.
Subject Keywords
find...By, read...By, query...By, count...By, get...By, exists...By, delete...By
Predicate Keywords
And, Or, Is, Equals, Between, LessThan, GreaterThan, Like, In, OrderBy
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// Find by single propertyList<User>findByEmail(String email);List<User>findByLastName(String lastName);Optional<User>findByUsername(String username);// Find by multiple propertiesList<User>findByFirstNameAndLastName(String firstName,String lastName);List<User>findByFirstNameOrLastName(String firstName,String lastName);// Comparison operatorsList<User>findByAgeGreaterThan(int age);List<User>findByAgeLessThanEqual(int age);List<User>findByAgeBetween(int startAge,int endAge);// String matchingList<User>findByEmailContaining(String keyword);List<User>findByEmailStartingWith(String prefix);List<User>findByEmailEndingWith(String suffix);List<User>findByFirstNameIgnoreCase(String firstName);// Null checksList<User>findByMiddleNameIsNull();List<User>findByMiddleNameIsNotNull();// Boolean checksList<User>findByActiveTrue();List<User>findByActiveFalse();// Collection operationsList<User>findByAgeIn(Collection<Integer> ages);List<User>findByAgeNotIn(Collection<Integer> ages);// OrderingList<User>findByLastNameOrderByFirstNameAsc(String lastName);List<User>findByActiveOrderByCreatedAtDesc(boolean active);// Limiting resultsUserfindFirstByOrderByCreatedAtDesc();List<User>findTop5ByActiveOrderByCreatedAtDesc(boolean active);// Count and existslongcountByActive(boolean active);booleanexistsByEmail(String email);// DeletevoiddeleteByEmail(String email);longdeleteByActiveFalse();}@Query Annotation
For more complex queries that can't be expressed through method naming, use the @Query annotation with JPQL (Java Persistence Query Language) or native SQL.
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// JPQL query (uses entity names and properties)@Query("SELECT u FROM User u WHERE u.email = ?1")UserfindByEmailAddress(String email);// Named parameters (recommended)@Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.lastName = :lastName")List<User>findByFullName(@Param("firstName")String firstName,@Param("lastName")String lastName);// JPQL with LIKE@Query("SELECT u FROM User u WHERE u.email LIKE %:keyword%")List<User>searchByEmail(@Param("keyword")String keyword);// JPQL JOIN query@Query("SELECT u FROM User u JOIN u.roles r WHERE r.name = :roleName")List<User>findByRoleName(@Param("roleName")String roleName);// JPQL with ORDER BY@Query("SELECT u FROM User u WHERE u.active = true ORDER BY u.createdAt DESC")List<User>findActiveUsersOrderByRecent();// Native SQL query (uses table/column names)@Query(value ="SELECT * FROM users WHERE status = ?1", nativeQuery =true)List<User>findByStatusNative(String status);// Native query with named parameters@Query(value ="SELECT * FROM users WHERE email LIKE :pattern", nativeQuery =true)List<User>searchEmailNative(@Param("pattern")String pattern);// Projection - return specific fields@Query("SELECT u.firstName, u.lastName FROM User u WHERE u.active = true")List<Object[]>findActiveUserNames();// Update query@Modifying@Transactional@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")intdeactivateInactiveUsers(@Param("date")LocalDateTime date);// Delete query@Modifying@Transactional@Query("DELETE FROM User u WHERE u.email = :email")voiddeleteByEmailCustom(@Param("email")String email);}Important!
For @Modifying queries (UPDATE, DELETE), you must also add @Transactional annotation. The method returns the number of affected rows.
Pagination and Sorting
Spring Data JPA provides built-in support for pagination and sorting using Pageable and Sort objects.
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// Pageable returns Page<User> with metadataPage<User>findByActive(boolean active,Pageable pageable);// Slice is lighter - doesn't count totalSlice<User>findByLastName(String lastName,Pageable pageable);// List with pagination (no metadata)List<User>findByFirstName(String firstName,Pageable pageable);// With Sort parameterList<User>findByActive(boolean active,Sort sort);// Combined with @Query@Query("SELECT u FROM User u WHERE u.department = :dept")Page<User>findByDepartment(@Param("dept")String department,Pageable pageable);}@ServicepublicclassUserService{@AutowiredprivateUserRepository userRepository;publicPage<User>getActiveUsers(int page,int size){// Create Pageable object (page is zero-indexed)Pageable pageable =PageRequest.of(page, size);return userRepository.findByActive(true, pageable);}publicPage<User>getActiveUsersSorted(int page,int size){// With sortingPageable pageable =PageRequest.of(page, size,Sort.by("createdAt").descending());return userRepository.findByActive(true, pageable);}publicPage<User>getUsersWithMultipleSorts(int page,int size){// Multiple sort criteriaSort sort =Sort.by(Sort.Order.desc("active"),Sort.Order.asc("lastName"),Sort.Order.asc("firstName"));Pageable pageable =PageRequest.of(page, size, sort);return userRepository.findAll(pageable);}publicList<User>getUsersSortedOnly(){// Just sorting, no paginationSort sort =Sort.by(Sort.Direction.ASC,"lastName");return userRepository.findByActive(true, sort);}}// Using Page objectPage<User> page = userService.getActiveUsers(0,10);List<User> users = page.getContent();// The actual dataint totalPages = page.getTotalPages();// Total number of pageslong totalElements = page.getTotalElements();// Total number of recordsint currentPage = page.getNumber();// Current page number (0-indexed)int pageSize = page.getSize();// Page sizeboolean hasNext = page.hasNext();// Has more pages?boolean hasPrevious = page.hasPrevious();// Has previous pages?Query Keywords Reference
Complete reference of supported keywords for derived query methods:
| Keyword | Sample | JPQL Equivalent |
|---|---|---|
And | findByFirstNameAndLastName | ... WHERE x.firstName = ?1 AND x.lastName = ?2 |
Or | findByFirstNameOrLastName | ... WHERE x.firstName = ?1 OR x.lastName = ?2 |
Between | findByAgeBetween | ... WHERE x.age BETWEEN ?1 AND ?2 |
LessThan | findByAgeLessThan | ... WHERE x.age < ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | ... WHERE x.age >= ?1 |
Like | findByNameLike | ... WHERE x.name LIKE ?1 |
Containing | findByNameContaining | ... WHERE x.name LIKE %?1% |
In | findByAgeIn | ... WHERE x.age IN ?1 |
True / False | findByActiveTrue | ... WHERE x.active = TRUE |
IsNull / IsNotNull | findByNameIsNull | ... WHERE x.name IS NULL |
OrderBy | findByNameOrderByAgeDesc | ... WHERE x.name = ?1 ORDER BY x.age DESC |
Best Practices
Use Named Parameters
Prefer :paramName over positional parameters ?1 for better readability and maintainability.
Return Optional for Single Results
Use Optional<Entity> when expecting 0 or 1 result to handle null cases gracefully.
Use Projections for Partial Data
When you only need specific fields, use interface or class-based projections to improve performance.
Add Indexes for Query Fields
Ensure database indexes exist on columns frequently used in WHERE clauses for optimal query performance.