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.

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
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 @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.