Saltar al contenido principal

Ejercicios de JPA con Query Methods

Introducción breve y referencias

En este post encontrarás 50 ejercicios que muestran distintas funcionalidades de los Query Methods con Spring Data JPA (métodos derivados por nombre) usando JpaRepository.
Para la referencia oficial sobre la creación de queries a partir de nombres de métodos y la lista de palabras clave soportadas por Spring Data JPA, consulta la documentación oficial de Spring Data JPA.

Fuentes recomendadas:

Especificación de los modelos​

A continuación se definen las entidades que se usarán como referencia en los ejercicios. Puedes copiarlas directamente en tu proyecto Spring Boot y rellenar sus métodos faltantes usando Lombok.

Especificación del modelo a revisar​

A continuación se definen las entidades Java (anotaciones JPA) que usaremos como referencia en los ejercicios.

Especificación del modelo a revisar
package com.example.demo.model;

import jakarta.persistence.*;
import java.sql.Timestamp;
import java.util.*;

@Entity
@Table(name = "departments")
public class Department {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// getters / setters
}

@Entity
@Table(name = "instructors")
public class Instructor {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private Timestamp hireDate;
private boolean active;

@ManyToOne
private Department department;
// getters / setters
}

@Entity
@Table(name = "courses")
public class Course {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code; // e.g. CS101
private String title;
private int credits;
private Timestamp startDate;
private boolean active;

@Enumerated(EnumType.STRING)
private Level level; // BEGINNER / INTERMEDIATE / ADVANCED

@ManyToOne
private Department department;

@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
// getters / setters
}

@Entity
@Table(name = "students")
public class Student {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String username;
private String email;
private String firstName;
private String lastName;
private Integer age;
private Double gpa;
private boolean active;
private Timestamp registrationDate;

private String city;
private String state;

@ManyToMany
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses = new HashSet<>();

@ManyToOne
private Instructor advisor;

@OneToMany(mappedBy = "student")
private List<Enrollment> enrollments = new ArrayList<>();
// getters / setters
}

@Entity
@Table(name = "enrollments")
public class Enrollment {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
private Student student;

@ManyToOne
private Course course;

@Enumerated(EnumType.STRING)
private Semester semester; // SPRING, FALL, etc.

private Double grade;

@Enumerated(EnumType.STRING)
private EnrollmentStatus status; // ENROLLED, DROPPED, COMPLETED
// getters / setters
}

public enum Level {
BEGINNER, INTERMEDIATE, ADVANCED
}

public enum Semester {
SPRING, SUMMER, FALL, WINTER
}

public enum EnrollmentStatus {
ENROLLED, DROPPED, COMPLETED
}

Nota: las entidades previas muestran los campos y relaciones básicos.

Ejercicios de QueryMethods usando JpaRepository​

A continuación vienen algunos ejercicios para practicar la creación de query methods usando JpaRepository. Cada ejercicio incluye:

  • Un subtítulo con el nombre del query method.
  • Una descripción de lo que se pide.
  • Una sección <details> con la solución esperada: la firma del método en el Repository y un ejemplo de uso (servicio o test).

findByUsername​

Qué se requiere: Encuentra todos los estudiantes cuyo username sea igual a un string dado.

Solución esperada
findByUsername
public interface StudentRepository extends JpaRepository<Student, Long> {
List<Student> findByUsername(String username);
}

Ejemplo de uso:

findByUsername (2)
@Autowired
private StudentRepository repo;

public List<Student> findStudentsByUsername(String username) {
return repo.findByUsername(username);
}

findByEmailIgnoreCase​

Qué se requiere: Busca estudiantes por email, ignorando mayúsculas/minúsculas.

Solución esperada
findByEmailIgnoreCase
List<Student> findByEmailIgnoreCase(String email);

Uso:

findByEmailIgnoreCase (2)
List<Student> s = repo.findByEmailIgnoreCase("ALICE@EXAMPLE.COM");
// Encuentra registros aunque el email esté en distinta capitalización.

findByFirstNameAndLastName​

Qué se requiere: Busca estudiantes por firstName y lastName.

Solución esperada
findByFirstNameAndLastName
List<Student> findByFirstNameAndLastName(String firstName, String lastName);

Uso:

findByFirstNameAndLastName (2)
repo.findByFirstNameAndLastName("Alice", "Lopez");

findByAgeGreaterThan​

Qué se requiere: Encuentra estudiantes con age mayor a un valor.

Solución esperada
findByAgeGreaterThan
List<Student> findByAgeGreaterThan(Integer age);

Uso:

findByAgeGreaterThan (2)
repo.findByAgeGreaterThan(21);

findByAgeGreaterThanEqual​

Qué se requiere: Encuentra estudiantes con age mayor o igual a un valor.

Solución esperada
findByAgeGreaterThanEqual
List<Student> findByAgeGreaterThanEqual(Integer age);

findByGpaBetween​

Qué se requiere: Encuentra estudiantes con gpa dentro de un rango (inclusive).

Solución esperada
findByGpaBetween
List<Student> findByGpaBetween(Double min, Double max);

Uso:

findByGpaBetween (2)
repo.findByGpaBetween(3.0, 4.0);

findByActiveTrue​

Qué se requiere: Encuentra estudiantes activos (campo boolean active = true).

Solución esperada
findByActiveTrue
List<Student> findByActiveTrue();

Uso:

findByActiveTrue (2)
repo.findByActiveTrue();

findByActiveFalse​

Qué se requiere: Encuentra estudiantes inactivos (campo boolean active = false).

Solución esperada
findByActiveFalse
List<Student> findByActiveFalse();

findByRegistrationDateAfter​

Qué se requiere: Encuentra estudiantes que se registraron después de una fecha dada.

Solución esperada
findByRegistrationDateAfter
List<Student> findByRegistrationDateAfter(Timestamp date);

Uso:

findByRegistrationDateAfter (2)
repo.findByRegistrationDateAfter(Timestamp.valueOf(LocalDateTime.of(2024, 1, 1, 0, 0)));

findByRegistrationDateBetween​

Qué se requiere: Encuentra estudiantes registrados entre dos fechas.

Solución esperada
findByRegistrationDateBetween
List<Student> findByRegistrationDateBetween(Timestamp from, Timestamp to);

Uso:

findByRegistrationDateBetween (2)
repo.findByRegistrationDateBetween(Timestamp.valueOf(LocalDateTime.of(2024,1,1,0,0)), Timestamp.valueOf(LocalDateTime.of(2024,12,31,23,59)));

findByCoursesCode​

Qué se requiere: Encuentra estudiantes que estén inscritos en un curso con un código dado (join implicito con collection courses).

Solución esperada
findByCoursesCode
List<Student> findByCoursesCode(String code);

Uso:

findByCoursesCode (2)
repo.findByCoursesCode("CS101");

findByCoursesTitleContainingIgnoreCase​

Qué se requiere: Busca estudiantes que tengan cursos cuyo título contenga una subcadena (case-insensitive).

Solución esperada
findByCoursesTitleContainingIgnoreCase
List<Student> findByCoursesTitleContainingIgnoreCase(String titlePart);

Uso:

findByCoursesTitleContainingIgnoreCase (2)
repo.findByCoursesTitleContainingIgnoreCase("data");

findDistinctByCoursesCode​

Qué se requiere: Encuentra estudiantes distintos (sin duplicados) inscritos en el curso code.

Solución esperada
findDistinctByCoursesCode
List<Student> findDistinctByCoursesCode(String code);

Uso:

findDistinctByCoursesCode (2)
repo.findDistinctByCoursesCode("CS101");

findByAdvisorLastName​

Qué se requiere: Encuentra estudiantes cuyo advisor (ManyToOne Instructor) tiene un apellido dado.

Solución esperada
findByAdvisorLastName
List<Student> findByAdvisorLastName(String lastName);

Uso:

findByAdvisorLastName (2)
repo.findByAdvisorLastName("Gonzalez");

findByAdvisorId​

Qué se requiere: Encuentra estudiantes por el id del advisor.

Solución esperada
findByAdvisorId
List<Student> findByAdvisorId(Long instructorId);

Uso:

findByAdvisorId (2)
repo.findByAdvisorId(42L);

findByAdvisorIsNull​

Qué se requiere: Encuentra estudiantes que no tienen advisor asignado.

Solución esperada
findByAdvisorIsNull
List<Student> findByAdvisorIsNull();

Uso:

findByAdvisorIsNull (2)
repo.findByAdvisorIsNull();

existsByEmail​

Qué se requiere: Comprueba si existe un estudiante con un email dado.

Solución esperada
existsByEmail
boolean existsByEmail(String email);

Uso:

existsByEmail (2)
boolean exists = repo.existsByEmail("alice@example.com");

countByActiveTrue​

Qué se requiere: Cuenta cuantos estudiantes están activos.

Solución esperada
countByActiveTrue
long countByActiveTrue();

Uso:

countByActiveTrue (2)
long activeStudents = repo.countByActiveTrue();

deleteByUsername​

Qué se requiere: Borra estudiantes por username (método reservado: delete...).

Solución esperada
deleteByUsername
void deleteByUsername(String username);

Uso:

deleteByUsername (2)
repo.deleteByUsername("old_user");

findTop5ByOrderByGpaDesc​

Qué se requiere: Devuelve los 5 estudiantes con mayor GPA (Top / OrderBy).

Solución esperada
findTop5ByOrderByGpaDesc
List<Student> findTop5ByOrderByGpaDesc();

Uso:

findTop5ByOrderByGpaDesc (2)
List<Student> top5 = repo.findTop5ByOrderByGpaDesc();

findFirstByOrderByRegistrationDateAsc​

Qué se requiere: Encuentra el primer estudiante (el más antiguo) por fecha de registro.

Solución esperada
findFirstByOrderByRegistrationDateAsc
Optional<Student> findFirstByOrderByRegistrationDateAsc();

Uso:

findFirstByOrderByRegistrationDateAsc (2)
Optional<Student> first = repo.findFirstByOrderByRegistrationDateAsc();

findByFirstNameStartingWith​

Qué se requiere: Busca estudiantes cuyo firstName empieza con un prefijo.

Solución esperada
findByFirstNameStartingWith
List<Student> findByFirstNameStartingWith(String prefix);

Uso:

findByFirstNameStartingWith (2)
repo.findByFirstNameStartingWith("Al");

findByLastNameEndingWith​

Qué se requiere: Busca estudiantes cuyo lastName termina con un sufijo.

Solución esperada
findByLastNameEndingWith
List<Student> findByLastNameEndingWith(String suffix);

Uso:

findByLastNameEndingWith (2)
repo.findByLastNameEndingWith("ez");

findByFirstNameContaining​

Qué se requiere: Busca estudiantes cuyo firstName contiene una subcadena.

Solución esperada
findByFirstNameContaining
List<Student> findByFirstNameContaining(String fragment);

Uso:

findByFirstNameContaining (2)
repo.findByFirstNameContaining("li");

findByEmailLike​

Qué se requiere: Busca estudiantes con email usando LIKE (usa % para comodines).

Solución esperada
findByEmailLike
List<Student> findByEmailLike(String pattern);

Uso:

findByEmailLike (2)
repo.findByEmailLike("%example.com");

findByGpaIsNull​

Qué se requiere: Encuentra estudiantes cuyo gpa es NULL.

Solución esperada
findByGpaIsNull
List<Student> findByGpaIsNull();

findByGpaIsNotNull​

Qué se requiere: Encuentra estudiantes cuyo gpa no es NULL.

Solución esperada
findByGpaIsNotNull
List<Student> findByGpaIsNotNull();

findByUsernameIn​

Qué se requiere: Encuentra estudiantes cuyo username está en una colección dada (IN).

Solución esperada
findByUsernameIn
List<Student> findByUsernameIn(Collection<String> usernames);

Uso:

findByUsernameIn (2)
repo.findByUsernameIn(List.of("a","b","c"));

findByUsernameNotIn​

Qué se requiere: Encuentra estudiantes cuyo username NO está en una colección dada (NOT IN).

Solución esperada
findByUsernameNotIn
List<Student> findByUsernameNotIn(Collection<String> usernames);

findByUsernameNot​

Qué se requiere: Encuentra estudiantes cuyo username NO sea el dado (NOT).

Solución esperada
findByUsernameNot
List<Student> findByUsernameNot(String username);

findByDepartmentName (CourseRepository)​

Qué se requiere: En CourseRepository: busca cursos por el nombre del department asociado.

Solución esperada
findByDepartmentName (CourseRepository)
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findByDepartmentName(String deptName);
}

Uso:

findByDepartmentName (CourseRepository) (2)
courseRepo.findByDepartmentName("Computer Science");

findByCreditsLessThan (CourseRepository)​

Qué se requiere: Encuentra cursos con menos de N créditos.

Solución esperada
findByCreditsLessThan (CourseRepository)
List<Course> findByCreditsLessThan(int credits);

Uso:

findByCreditsLessThan (CourseRepository) (2)
courseRepo.findByCreditsLessThan(4);

findByLevelIn (CourseRepository)​

Qué se requiere: Encuentra cursos cuyo level está dentro de una colección de niveles.

Solución esperada
findByLevelIn (CourseRepository)
List<Course> findByLevelIn(Collection<Level> levels);

Uso:

findByLevelIn (CourseRepository) (2)
courseRepo.findByLevelIn(List.of(Level.BEGINNER, Level.INTERMEDIATE));

findByEnrollmentsGradeGreaterThan​

Qué se requiere: Encuentra estudiantes que tengan alguna inscripción (enrollments) con nota mayor a X.

Solución esperada
findByEnrollmentsGradeGreaterThan
List<Student> findByEnrollmentsGradeGreaterThan(Double grade);

Uso:

findByEnrollmentsGradeGreaterThan (2)
repo.findByEnrollmentsGradeGreaterThan(85.0);

findByEnrollmentsStatus​

Qué se requiere: Encuentra estudiantes según el estado de alguna de sus inscripciones.

Solución esperada
findByEnrollmentsStatus
List<Student> findByEnrollmentsStatus(EnrollmentStatus status);

Uso:

findByEnrollmentsStatus (2)
repo.findByEnrollmentsStatus(EnrollmentStatus.COMPLETED);

findByEnrollmentsSemesterAndCourseCode​

Qué se requiere: Encuentra estudiantes inscritos en un curso específico durante un semestre concreto.

Solución esperada
findByEnrollmentsSemesterAndCourseCode
List<Student> findByEnrollmentsSemesterAndEnrollmentsCourseCode(Semester semester, String courseCode);

Uso:

findByEnrollmentsSemesterAndCourseCode (2)
repo.findByEnrollmentsSemesterAndEnrollmentsCourseCode(Semester.FALL, "CS101");

findByCoursesLevelAndCreditsGreaterThan​

Qué se requiere: Encuentra estudiantes que tienen al menos un curso con un level y más de N créditos.

Solución esperada
findByCoursesLevelAndCreditsGreaterThan
List<Student> findByCoursesLevelAndCoursesCreditsGreaterThan(Level level, int credits);

Uso:

findByCoursesLevelAndCreditsGreaterThan (2)
repo.findByCoursesLevelAndCoursesCreditsGreaterThan(Level.ADVANCED, 3);

findByActiveTrue (paginated)​

Qué se requiere: Paginación: devuelve estudiantes activos con Pageable.

Solución esperada
findByActiveTrue (paginated)
Page<Student> findByActiveTrue(Pageable pageable);

Uso:

findByActiveTrue (paginated) (2)
Page<Student> page = repo.findByActiveTrue(PageRequest.of(0, 20, Sort.by("gpa").descending()));

findByCoursesStartDateBefore​

Qué se requiere: Encuentra estudiantes que tienen cursos cuyo startDate es anterior a una fecha.

Solución esperada
findByCoursesStartDateBefore
List<Student> findByCoursesStartDateBefore(Timestamp date);

Uso:

findByCoursesStartDateBefore (2)
repo.findByCoursesStartDateBefore(Timestamp.valueOf(LocalDateTime.now()));

findByFirstNameIgnoreCaseAndLastNameIgnoreCase​

Qué se requiere: Busca comparando firstName y lastName ignorando mayúsculas/minúsculas.

Solución esperada
findByFirstNameIgnoreCaseAndLastNameIgno
List<Student> findByFirstNameIgnoreCaseAndLastNameIgnoreCase(String first, String last);

findDistinctByFirstNameAndLastName​

Qué se requiere: Devuelve resultados distintos al filtrar por nombre y apellido.

Solución esperada
findDistinctByFirstNameAndLastName
List<Student> findDistinctByFirstNameAndLastName(String first, String last);

findByEmailEndingWith​

Qué se requiere: Busca estudiantes cuyo email termina con una cadena dada.

Solución esperada
findByEmailEndingWith
List<Student> findByEmailEndingWith(String suffix);
// Ej: findByEmailEndingWith("@gmail.com")

findByFirstNameOrderByLastNameAsc​

Qué se requiere: Ordena: devuelve estudiantes con un firstName dado ordenados por lastName ascendente.

Solución esperada
findByFirstNameOrderByLastNameAsc
List<Student> findByFirstNameOrderByLastNameAsc(String firstName);

findByLastNameOrderByFirstNameDesc​

Qué se requiere: Ordena por firstName descendente.

Solución esperada
findByLastNameOrderByFirstNameDesc
List<Student> findByLastNameOrderByFirstNameDesc(String lastName);

findByCoursesCodeOrderByCreditsDesc​

Qué se requiere: Busca estudiantes por código de curso y ordena por créditos (desc).

Solución esperada
findByCoursesCodeOrderByCreditsDesc
List<Student> findByCoursesCodeOrderByCoursesCreditsDesc(String courseCode);

Uso:

findByCoursesCodeOrderByCreditsDesc (2)
repo.findByCoursesCodeOrderByCoursesCreditsDesc("CS101");

streamByActiveTrue​

Qué se requiere: Devuelve un Stream de estudiantes activos (útil para procesar grandes resultados).

Solución esperada
streamByActiveTrue
Stream<Student> streamByActiveTrue();

Uso (recordar cerrar el stream si es necesario):

streamByActiveTrue (2)
try (Stream<Student> s = repo.streamByActiveTrue()) {
s.forEach(...);
}

findTopByOrderByGpaAsc​

Qué se requiere: Devuelve el estudiante con menor GPA.

Solución esperada
findTopByOrderByGpaAsc
Optional<Student> findTopByOrderByGpaAsc();

Uso:

findTopByOrderByGpaAsc (2)
Optional<Student> worst = repo.findTopByOrderByGpaAsc();

findByRegistrationDateYear (ejemplo con Between)​

Qué se requiere: Busca estudiantes registrados en un año determinado usando Between en fechas.

Solución esperada
findByRegistrationDateYear
List<Student> findByRegistrationDateBetween(Timestamp startOfYear, Timestamp endOfYear);
// Ejemplo de llamada:
// repo.findByRegistrationDateBetween(Timestamp.valueOf(LocalDateTime.of(2024,1,1,0,0)), Timestamp.valueOf(LocalDateTime.of(2024,12,31,23,59)));

findByFirstNameNot​

Qué se requiere: Encuentra estudiantes cuyo firstName no sea el dado.

Solución esperada
findByFirstNameNot
List<Student> findByFirstNameNot(String name);

findByEmailContainingIgnoreCaseAndActiveTrue​

Qué se requiere: Combinación compleja: email contiene X (ignoring case) y active = true.

Solución esperada
findByEmailContainingIgnoreCaseAndActive
List<Student> findByEmailContainingIgnoreCaseAndActiveTrue(String fragment);

Uso:

findByEmailContainingIgnoreCaseAndActive (2)
repo.findByEmailContainingIgnoreCaseAndActiveTrue("example");

findDistinctByCoursesDepartmentName​

Qué se requiere: Devuelve estudiantes distintos que estén en cursos de un depto con nombre dado.

Solución esperada
findDistinctByCoursesDepartmentName
List<Student> findDistinctByCoursesDepartmentName(String deptName);

Uso:

findDistinctByCoursesDepartmentName (2)
repo.findDistinctByCoursesDepartmentName("Mathematics");

findByEnrollmentsCourseCodeAndEnrollmentsStatus​

Qué se requiere: Encuentra estudiantes por código de curso y estado de la inscripción.

Solución esperada
findByEnrollmentsCourseCodeAndEnrollment
List<Student> findByEnrollmentsCourseCodeAndEnrollmentsStatus(String courseCode, EnrollmentStatus status);

Uso:

findByEnrollmentsCourseCodeAndEnrollment (2)
repo.findByEnrollmentsCourseCodeAndEnrollmentsStatus("CS101", EnrollmentStatus.ENROLLED);

existsByUsername​

Qué se requiere: Comprueba existencia por username.

Solución esperada
existsByUsername
boolean existsByUsername(String username);

Uso:

existsByUsername (2)
boolean e = repo.existsByUsername("kevin");

deleteAllByActiveFalse​

Qué se requiere: Borra todos los estudiantes inactivos (método reservado deleteAllBy...).

Solución esperada
deleteAllByActiveFalse
void deleteAllByActiveFalse();

Uso:

deleteAllByActiveFalse (2)
repo.deleteAllByActiveFalse();

findByFirstNameOrLastName​

Qué se requiere: Busca estudiantes cuyo firstName O lastName coincida con los parámetros.

Solución esperada
findByFirstNameOrLastName
List<Student> findByFirstNameOrLastName(String first, String last);

Uso:

findByFirstNameOrLastName (2)
repo.findByFirstNameOrLastName("Carlos","Gomez");

findByFirstNameNotContaining​

Qué se requiere: Encuentra estudiantes cuyo firstName NO contenga una subcadena.

Solución esperada
findByFirstNameNotContaining
List<Student> findByFirstNameNotContaining(String fragment);

Uso:

findByFirstNameNotContaining (2)
repo.findByFirstNameNotContaining("test");

Notas finales​

  • Muchas de las palabras clave soportadas por Spring Data JPA (por ejemplo And, Or, Between, LessThan, GreaterThan, Like, OrderBy, Distinct, Top, First, IgnoreCase, IsNull, IsNotNull, etc.) permiten expresar condiciones potentes sin necesidad de escribir JPQL explícito.
  • Cuando la expresión derivada se vuelva muy compleja (muchos joins, subselects, condiciones dinámicas), considera usar @Query con JPQL/SQL, QueryDSL o Specification/Criteria API.
  • Para opciones de paginación y ordenamiento es preferible usar Pageable y Sort desde los parámetros de método en lugar de crear métodos con OrderBy muy largos.