Quiz: Spring Data JPA Avanzado
Ponga a prueba sus conocimientos sobre Spring Data JPA, las relaciones y las consultas.
30 preguntas - Haga clic en cada pregunta para revelar la respuesta.
Preguntas 1-10: Convenciones y Consultas
Pregunta 1: ¿Qué son los Query Methods de Spring Data JPA?
Respuesta: Métodos cuyo nombre define automáticamente la consulta:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByEmail(String email);
List<User> findByLastNameAndFirstName(String ln, String fn);
List<User> findByAgeGreaterThan(int age);
}
Spring genera automáticamente la implementación SQL.
Pregunta 2: ¿Cuáles son las palabras clave de consulta más comunes?
Respuesta:
| Palabra clave | Ejemplo |
|---|---|
| And | findByNameAndAge |
| Or | findByNameOrEmail |
| Between | findByAgeBetween |
| LessThan | findByAgeLessThan |
| GreaterThan | findByAgeGreaterThan |
| Like | findByNameLike |
| Containing | findByNameContaining |
| OrderBy | findByAgeOrderByNameAsc |
| Not | findByNameNot |
| In | findByAgeIn(Collection) |
| True/False | findByActiveTrue |
Pregunta 3: ¿Cómo limitar el número de resultados?
Respuesta:
// Top/First
User findFirstByOrderByCreatedAtDesc();
List<User> findTop10ByOrderByScoreDesc();
// Con Pageable
Page<User> findAll(Pageable pageable);
Pregunta 4: ¿Cómo escribir una consulta JPQL personalizada?
Respuesta: Con @Query:
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
List<User> findByEmailDomain(@Param("domain") String domain);
@Query("SELECT u FROM User u WHERE u.age > :age ORDER BY u.name")
List<User> findOlderThan(@Param("age") int age);
Pregunta 5: ¿Cómo escribir una consulta SQL nativa?
Respuesta:
@Query(value = "SELECT * FROM users WHERE email LIKE %?1", nativeQuery = true)
List<User> findByEmailNative(String domain);
@Query(value = "SELECT COUNT(*) FROM users", nativeQuery = true)
long countAllNative();
Pregunta 6: ¿Cómo hacer una consulta de actualización?
Respuesta:
@Modifying
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("date") LocalDate date);
@Modifying
@Query("DELETE FROM User u WHERE u.active = false")
int deleteInactiveUsers();
@Modifying es obligatorio para UPDATE/DELETE.
Pregunta 7: ¿Cómo proyectar solo ciertos campos?
Respuesta: Con proyecciones:
// Interface projection
public interface UserSummary {
String getName();
String getEmail();
}
List<UserSummary> findByActiveTrue();
// DTO projection
@Query("SELECT new com.example.UserDto(u.name, u.email) FROM User u")
List<UserDto> findAllAsDto();
Pregunta 8: ¿Cómo verificar la existencia de una entidad?
Respuesta:
boolean existsByEmail(String email);
boolean existsByIdAndActiveTrue(Long id);
// Evita cargar la entidad completa
Pregunta 9: ¿Cómo contar las entidades?
Respuesta:
long countByActiveTrue();
long countByDepartment(String department);
@Query("SELECT COUNT(u) FROM User u WHERE u.age > :age")
long countOlderThan(@Param("age") int age);
Pregunta 10: ¿Cómo eliminar por criterios?
Respuesta:
void deleteByEmail(String email);
long deleteByActivefalse(); // Devuelve la cantidad eliminada
@Modifying
@Query("DELETE FROM User u WHERE u.createdAt < :date")
int deleteOldUsers(@Param("date") LocalDate date);
Preguntas 11-20: Relaciones
Pregunta 11: ¿Cómo definir una relación OneToMany?
Respuesta:
@Entity
public class Department {
@Id
private Long id;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
private List<Employee> employees = new ArrayList<>();
}
@Entity
public class Employee {
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
}
Pregunta 12: ¿Qué es mappedBy?
Respuesta: mappedBy indica el lado no propietario de la relación. Apunta al campo de la otra entidad que posee la clave foránea.
@OneToMany(mappedBy = "department") // "department" es el campo en Employee
private List<Employee> employees;
Pregunta 13: ¿Cómo definir una relación ManyToMany?
Respuesta:
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
public class Course {
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
Pregunta 14: ¿Cuál es la diferencia entre FetchType.LAZY y EAGER?
Respuesta:
- LAZY: carga solo cuando se accede (recomendado)
- EAGER: carga inmediatamente con la entidad principal
@OneToMany(fetch = FetchType.LAZY) // Por defecto para colecciones
private List<Order> orders;
@ManyToOne(fetch = FetchType.EAGER) // Por defecto para single
private User user;
Pregunta 15: ¿Qué es el problema N+1?
Respuesta: Cuando se cargan N entidades y luego se hacen N consultas adicionales para las relaciones:
// 1 consulta para los departamentos
List<Department> deps = repo.findAll();
// N consultas para los empleados (1 por departamento)
deps.forEach(d -> d.getEmployees().size());
Solución: JOIN FETCH o @EntityGraph.
Pregunta 16: ¿Cómo resolver el problema N+1 con JOIN FETCH?
Respuesta:
@Query("SELECT d FROM Department d JOIN FETCH d.employees")
List<Department> findAllWithEmployees();
Una sola consulta carga todo.
Pregunta 17: ¿Qué es un @EntityGraph?
Respuesta: Define qué relaciones cargar:
@EntityGraph(attributePaths = {"employees", "manager"})
List<Department> findAll();
// O definido en la entidad
@NamedEntityGraph(
name = "Department.full",
attributeNodes = @NamedAttributeNode("employees")
)
@Entity
public class Department { }
@EntityGraph("Department.full")
List<Department> findByName(String name);
Pregunta 18: ¿Cómo manejar las cascadas?
Respuesta:
@OneToMany(cascade = CascadeType.ALL) // Todas las operaciones
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
// Tipos:
// PERSIST - guardar
// MERGE - actualizar
// REMOVE - eliminar
// REFRESH - refrescar
// DETACH - separar
// ALL - todo
Pregunta 19: ¿Qué es orphanRemoval?
Respuesta: Elimina automáticamente los hijos huérfanos:
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children;
// Si se elimina un hijo de la lista, se elimina de la base de datos
parent.getChildren().remove(child); // DELETE en la base de datos
Pregunta 20: ¿Cómo evitar referencias circulares en JSON?
Respuesta:
// Opción 1: @JsonIgnore
@JsonIgnore
@ManyToOne
private Department department;
// Opción 2: @JsonManagedReference / @JsonBackReference
@JsonManagedReference
@OneToMany(mappedBy = "department")
private List<Employee> employees;
@JsonBackReference
@ManyToOne
private Department department;
// Opción 3: Usar DTOs (recomendado)
Preguntas 21-30: Paginación y Rendimiento
Pregunta 21: ¿Cómo implementar la paginación?
Respuesta:
// Repository
Page<User> findAll(Pageable pageable);
Page<User> findByActiveTrue(Pageable pageable);
// Service
Pageable pageable = PageRequest.of(0, 20, Sort.by("name"));
Page<User> page = userRepository.findAll(pageable);
// Resultado
page.getContent(); // Lista
page.getTotalElements(); // Total
page.getTotalPages(); // Número de páginas
page.getNumber(); // Página actual
Pregunta 22: ¿Cómo ordenar los resultados?
Respuesta:
// Por método
List<User> findByActiveOrderByNameAsc(boolean active);
// Por parámetro Sort
List<User> findByActive(boolean active, Sort sort);
userRepository.findByActive(true, Sort.by("name").descending());
// Multi-criterios
Sort sort = Sort.by("lastName").and(Sort.by("firstName"));
Pregunta 23: ¿Cómo usar Slice en lugar de Page?
Respuesta: Slice no cuenta el total (más eficiente):
Slice<User> findByActiveTrue(Pageable pageable);
// Resultado
slice.getContent();
slice.hasNext(); // ¿Hay una página siguiente?
slice.hasPrevious();
// No hay getTotalElements() ni getTotalPages()
Pregunta 24: ¿Cómo auditar las entidades (createdAt, updatedAt)?
Respuesta:
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class Auditable {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
private String createdBy;
}
// Activar
@EnableJpaAuditing
@Configuration
public class JpaConfig { }
Pregunta 25: ¿Cómo implementar el soft delete?
Respuesta:
@Entity
@Where(clause = "deleted = false") // Hibernate
public class User {
private boolean deleted = false;
}
// O con @SQLDelete
@SQLDelete(sql = "UPDATE users SET deleted = true WHERE id = ?")
Pregunta 26: ¿Cómo optimizar las consultas con índices?
Respuesta:
@Entity
@Table(indexes = {
@Index(name = "idx_email", columnList = "email"),
@Index(name = "idx_name_dept", columnList = "lastName, department")
})
public class User { }
Pregunta 27: ¿Qué es la caché de segundo nivel?
Respuesta: Caché compartida entre sesiones de Hibernate:
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product { }
// Configuración
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=...
Pregunta 28: ¿Cómo usar Specifications para consultas dinámicas?
Respuesta:
public interface UserRepository extends JpaSpecificationExecutor<User> { }
// Specification
Specification<User> hasName(String name) {
return (root, query, cb) -> cb.equal(root.get("name"), name);
}
Specification<User> isActive() {
return (root, query, cb) -> cb.isTrue(root.get("active"));
}
// Combinación
userRepository.findAll(hasName("John").and(isActive()));
Pregunta 29: ¿Cómo ejecutar consultas en batch?
Respuesta:
// Configuración
spring.jpa.properties.hibernate.jdbc.batch_size=50
// Código
@Transactional
public void saveAll(List<User> users) {
for (int i = 0; i < users.size(); i++) {
entityManager.persist(users.get(i));
if (i % 50 == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
Pregunta 30: ¿Cómo manejar las versiones para el optimistic locking?
Respuesta:
@Entity
public class Product {
@Version
private Long version;
}
// Si dos transacciones modifican simultáneamente,
// una lanza OptimisticLockException
Puntuación
| Puntuación | Nivel |
|---|---|
| 27-30 | Excelente - JPA dominado |
| 21-26 | Bien - Algunas revisiones |
| 15-20 | Medio - Repase los capítulos |
| Menos de 15 | A revisar |