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:
- Spring Data JPA — JPA Query Methods (reference)
- Spring Data JPA — Repository query keywords (lista de keywords soportadas)
- Baeldung — Spring Data JPA Query Methods
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.
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 elRepositoryy 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
public interface StudentRepository extends JpaRepository<Student, Long> {
List<Student> findByUsername(String username);
}
Ejemplo de uso:
@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
List<Student> findByEmailIgnoreCase(String email);
Uso:
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
List<Student> findByFirstNameAndLastName(String firstName, String lastName);
Uso:
repo.findByFirstNameAndLastName("Alice", "Lopez");
findByAgeGreaterThan
Qué se requiere: Encuentra estudiantes con age mayor a un valor.
Solución esperada
List<Student> findByAgeGreaterThan(Integer age);
Uso:
repo.findByAgeGreaterThan(21);
findByAgeGreaterThanEqual
Qué se requiere: Encuentra estudiantes con age mayor o igual a un valor.
Solución esperada
List<Student> findByAgeGreaterThanEqual(Integer age);
findByGpaBetween
Qué se requiere: Encuentra estudiantes con gpa dentro de un rango (inclusive).
Solución esperada
List<Student> findByGpaBetween(Double min, Double max);
Uso:
repo.findByGpaBetween(3.0, 4.0);
findByActiveTrue
Qué se requiere: Encuentra estudiantes activos (campo boolean active = true).
Solución esperada
List<Student> findByActiveTrue();
Uso:
repo.findByActiveTrue();
findByActiveFalse
Qué se requiere: Encuentra estudiantes inactivos (campo boolean active = false).
Solución esperada
List<Student> findByActiveFalse();
findByRegistrationDateAfter
Qué se requiere: Encuentra estudiantes que se registraron después de una fecha dada.
Solución esperada
List<Student> findByRegistrationDateAfter(Timestamp date);
Uso:
repo.findByRegistrationDateAfter(Timestamp.valueOf(LocalDateTime.of(2024, 1, 1, 0, 0)));
findByRegistrationDateBetween
Qué se requiere: Encuentra estudiantes registrados entre dos fechas.
Solución esperada
List<Student> findByRegistrationDateBetween(Timestamp from, Timestamp to);
Uso:
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
List<Student> findByCoursesCode(String code);
Uso:
repo.findByCoursesCode("CS101");
findByCoursesTitleContainingIgnoreCase
Qué se requiere: Busca estudiantes que tengan cursos cuyo título contenga una subcadena (case-insensitive).
Solución esperada
List<Student> findByCoursesTitleContainingIgnoreCase(String titlePart);
Uso:
repo.findByCoursesTitleContainingIgnoreCase("data");
findDistinctByCoursesCode
Qué se requiere: Encuentra estudiantes distintos (sin duplicados) inscritos en el curso code.
Solución esperada
List<Student> findDistinctByCoursesCode(String code);
Uso:
repo.findDistinctByCoursesCode("CS101");
findByAdvisorLastName
Qué se requiere: Encuentra estudiantes cuyo advisor (ManyToOne Instructor) tiene un apellido dado.
Solución esperada
List<Student> findByAdvisorLastName(String lastName);
Uso:
repo.findByAdvisorLastName("Gonzalez");
findByAdvisorId
Qué se requiere: Encuentra estudiantes por el id del advisor.
Solución esperada
List<Student> findByAdvisorId(Long instructorId);
Uso:
repo.findByAdvisorId(42L);
findByAdvisorIsNull
Qué se requiere: Encuentra estudiantes que no tienen advisor asignado.
Solución esperada
List<Student> findByAdvisorIsNull();
Uso:
repo.findByAdvisorIsNull();
existsByEmail
Qué se requiere: Comprueba si existe un estudiante con un email dado.
Solución esperada
boolean existsByEmail(String email);
Uso:
boolean exists = repo.existsByEmail("alice@example.com");
countByActiveTrue
Qué se requiere: Cuenta cuantos estudiantes están activos.
Solución esperada
long countByActiveTrue();
Uso:
long activeStudents = repo.countByActiveTrue();
deleteByUsername
Qué se requiere: Borra estudiantes por username (método reservado: delete...).
Solución esperada
void deleteByUsername(String username);
Uso:
repo.deleteByUsername("old_user");
findTop5ByOrderByGpaDesc
Qué se requiere: Devuelve los 5 estudiantes con mayor GPA (Top / OrderBy).
Solución esperada
List<Student> findTop5ByOrderByGpaDesc();
Uso:
List<Student> top5 = repo.findTop5ByOrderByGpaDesc();
findFirstByOrderByRegistrationDateAsc
Qué se requiere: Encuentra el primer estudiante (el más antiguo) por fecha de registro.
Solución esperada
Optional<Student> findFirstByOrderByRegistrationDateAsc();
Uso:
Optional<Student> first = repo.findFirstByOrderByRegistrationDateAsc();
findByFirstNameStartingWith
Qué se requiere: Busca estudiantes cuyo firstName empieza con un prefijo.
Solución esperada
List<Student> findByFirstNameStartingWith(String prefix);
Uso:
repo.findByFirstNameStartingWith("Al");
findByLastNameEndingWith
Qué se requiere: Busca estudiantes cuyo lastName termina con un sufijo.
Solución esperada
List<Student> findByLastNameEndingWith(String suffix);
Uso:
repo.findByLastNameEndingWith("ez");
findByFirstNameContaining
Qué se requiere: Busca estudiantes cuyo firstName contiene una subcadena.
Solución esperada
List<Student> findByFirstNameContaining(String fragment);
Uso:
repo.findByFirstNameContaining("li");
findByEmailLike
Qué se requiere: Busca estudiantes con email usando LIKE (usa % para comodines).
Solución esperada
List<Student> findByEmailLike(String pattern);
Uso:
repo.findByEmailLike("%example.com");
findByGpaIsNull
Qué se requiere: Encuentra estudiantes cuyo gpa es NULL.
Solución esperada
List<Student> findByGpaIsNull();
findByGpaIsNotNull
Qué se requiere: Encuentra estudiantes cuyo gpa no es NULL.
Solución esperada
List<Student> findByGpaIsNotNull();
findByUsernameIn
Qué se requiere: Encuentra estudiantes cuyo username está en una colección dada (IN).
Solución esperada
List<Student> findByUsernameIn(Collection<String> usernames);
Uso:
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
List<Student> findByUsernameNotIn(Collection<String> usernames);
findByUsernameNot
Qué se requiere: Encuentra estudiantes cuyo username NO sea el dado (NOT).
Solución esperada
List<Student> findByUsernameNot(String username);
findByDepartmentName (CourseRepository)
Qué se requiere: En CourseRepository: busca cursos por el nombre del department asociado.
Solución esperada
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findByDepartmentName(String deptName);
}
Uso:
courseRepo.findByDepartmentName("Computer Science");
findByCreditsLessThan (CourseRepository)
Qué se requiere: Encuentra cursos con menos de N créditos.
Solución esperada
List<Course> findByCreditsLessThan(int credits);
Uso:
courseRepo.findByCreditsLessThan(4);
findByLevelIn (CourseRepository)
Qué se requiere: Encuentra cursos cuyo level está dentro de una colección de niveles.
Solución esperada
List<Course> findByLevelIn(Collection<Level> levels);
Uso:
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
List<Student> findByEnrollmentsGradeGreaterThan(Double grade);
Uso:
repo.findByEnrollmentsGradeGreaterThan(85.0);
findByEnrollmentsStatus
Qué se requiere: Encuentra estudiantes según el estado de alguna de sus inscripciones.
Solución esperada
List<Student> findByEnrollmentsStatus(EnrollmentStatus status);
Uso:
repo.findByEnrollmentsStatus(EnrollmentStatus.COMPLETED);
findByEnrollmentsSemesterAndCourseCode
Qué se requiere: Encuentra estudiantes inscritos en un curso específico durante un semestre concreto.
Solución esperada
List<Student> findByEnrollmentsSemesterAndEnrollmentsCourseCode(Semester semester, String courseCode);
Uso:
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
List<Student> findByCoursesLevelAndCoursesCreditsGreaterThan(Level level, int credits);
Uso:
repo.findByCoursesLevelAndCoursesCreditsGreaterThan(Level.ADVANCED, 3);
findByActiveTrue (paginated)
Qué se requiere: Paginación: devuelve estudiantes activos con Pageable.
Solución esperada
Page<Student> findByActiveTrue(Pageable pageable);
Uso:
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
List<Student> findByCoursesStartDateBefore(Timestamp date);
Uso:
repo.findByCoursesStartDateBefore(Timestamp.valueOf(LocalDateTime.now()));
findByFirstNameIgnoreCaseAndLastNameIgnoreCase
Qué se requiere: Busca comparando firstName y lastName ignorando mayúsculas/minúsculas.
Solución esperada
List<Student> findByFirstNameIgnoreCaseAndLastNameIgnoreCase(String first, String last);
findDistinctByFirstNameAndLastName
Qué se requiere: Devuelve resultados distintos al filtrar por nombre y apellido.
Solución esperada
List<Student> findDistinctByFirstNameAndLastName(String first, String last);
findByEmailEndingWith
Qué se requiere: Busca estudiantes cuyo email termina con una cadena dada.
Solución esperada
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
List<Student> findByFirstNameOrderByLastNameAsc(String firstName);
findByLastNameOrderByFirstNameDesc
Qué se requiere: Ordena por firstName descendente.
Solución esperada
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
List<Student> findByCoursesCodeOrderByCoursesCreditsDesc(String courseCode);
Uso:
repo.findByCoursesCodeOrderByCoursesCreditsDesc("CS101");
streamByActiveTrue
Qué se requiere: Devuelve un Stream de estudiantes activos (útil para procesar grandes resultados).
Solución esperada
Stream<Student> streamByActiveTrue();
Uso (recordar cerrar el stream si es necesario):
try (Stream<Student> s = repo.streamByActiveTrue()) {
s.forEach(...);
}
findTopByOrderByGpaAsc
Qué se requiere: Devuelve el estudiante con menor GPA.
Solución esperada
Optional<Student> findTopByOrderByGpaAsc();
Uso:
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
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
List<Student> findByFirstNameNot(String name);
findByEmailContainingIgnoreCaseAndActiveTrue
Qué se requiere: Combinación compleja: email contiene X (ignoring case) y active = true.
Solución esperada
List<Student> findByEmailContainingIgnoreCaseAndActiveTrue(String fragment);
Uso:
repo.findByEmailContainingIgnoreCaseAndActiveTrue("example");
findDistinctByCoursesDepartmentName
Qué se requiere: Devuelve estudiantes distintos que estén en cursos de un depto con nombre dado.
Solución esperada
List<Student> findDistinctByCoursesDepartmentName(String deptName);
Uso:
repo.findDistinctByCoursesDepartmentName("Mathematics");
findByEnrollmentsCourseCodeAndEnrollmentsStatus
Qué se requiere: Encuentra estudiantes por código de curso y estado de la inscripción.
Solución esperada
List<Student> findByEnrollmentsCourseCodeAndEnrollmentsStatus(String courseCode, EnrollmentStatus status);
Uso:
repo.findByEnrollmentsCourseCodeAndEnrollmentsStatus("CS101", EnrollmentStatus.ENROLLED);
existsByUsername
Qué se requiere: Comprueba existencia por username.
Solución esperada
boolean existsByUsername(String username);
Uso:
boolean e = repo.existsByUsername("kevin");
deleteAllByActiveFalse
Qué se requiere: Borra todos los estudiantes inactivos (método reservado deleteAllBy...).
Solución esperada
void deleteAllByActiveFalse();
Uso:
repo.deleteAllByActiveFalse();
findByFirstNameOrLastName
Qué se requiere: Busca estudiantes cuyo firstName O lastName coincida con los parámetros.
Solución esperada
List<Student> findByFirstNameOrLastName(String first, String last);
Uso:
repo.findByFirstNameOrLastName("Carlos","Gomez");
findByFirstNameNotContaining
Qué se requiere: Encuentra estudiantes cuyo firstName NO contenga una subcadena.
Solución esperada
List<Student> findByFirstNameNotContaining(String fragment);
Uso:
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
@Querycon JPQL/SQL, QueryDSL o Specification/Criteria API. - Para opciones de paginación y ordenamiento es preferible usar
PageableySortdesde los parámetros de método en lugar de crear métodos conOrderBymuy largos.