Quiz: Spring Data JPA Avancé
Testez vos connaissances sur Spring Data JPA, les relations et les requêtes.
30 questions - Cliquez sur chaque question pour révéler la réponse.
Questions 1-10: Conventions et Requêtes
Question 1: Qu'est-ce que les Query Methods de Spring Data JPA?
Réponse: Des méthodes dont le nom définit la requête automatiquement:
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 génère l'implémentation SQL automatiquement.
Question 2: Quels sont les mots-clés de requête courants?
Réponse:
| Mot-clé | Exemple |
|---|---|
| And | findByNameAndAge |
| Or | findByNameOrEmail |
| Between | findByAgeBetween |
| LessThan | findByAgeLessThan |
| GreaterThan | findByAgeGreaterThan |
| Like | findByNameLike |
| Containing | findByNameContaining |
| OrderBy | findByAgeOrderByNameAsc |
| Not | findByNameNot |
| In | findByAgeIn(Collection) |
| True/False | findByActiveTrue |
Question 3: Comment limiter le nombre de résultats?
Réponse:
// Top/First
User findFirstByOrderByCreatedAtDesc();
List<User> findTop10ByOrderByScoreDesc();
// Avec Pageable
Page<User> findAll(Pageable pageable);
Question 4: Comment écrire une requête JPQL personnalisée?
Réponse: Avec @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);
Question 5: Comment écrire une requête SQL native?
Réponse:
@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();
Question 6: Comment faire une requête de mise à jour?
Réponse:
@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 est obligatoire pour UPDATE/DELETE.
Question 7: Comment projeter uniquement certains champs?
Réponse: Avec des projections:
// 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();
Question 8: Comment vérifier l'existence d'une entité?
Réponse:
boolean existsByEmail(String email);
boolean existsByIdAndActiveTrue(Long id);
// Évite de charger l'entité complète
Question 9: Comment compter les entités?
Réponse:
long countByActiveTrue();
long countByDepartment(String department);
@Query("SELECT COUNT(u) FROM User u WHERE u.age > :age")
long countOlderThan(@Param("age") int age);
Question 10: Comment supprimer par critères?
Réponse:
void deleteByEmail(String email);
long deleteByActivefalse(); // Retourne le nombre supprimé
@Modifying
@Query("DELETE FROM User u WHERE u.createdAt < :date")
int deleteOldUsers(@Param("date") LocalDate date);
Questions 11-20: Relations
Question 11: Comment définir une relation OneToMany?
Réponse:
@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;
}
Question 12: Qu'est-ce que mappedBy?
Réponse: mappedBy indique le côté non-propriétaire de la relation. Il pointe vers le champ de l'autre entité qui possède la clé étrangère.
@OneToMany(mappedBy = "department") // "department" est le champ dans Employee
private List<Employee> employees;
Question 13: Comment définir une relation ManyToMany?
Réponse:
@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<>();
}
Question 14: Quelle est la différence entre FetchType.LAZY et EAGER?
Réponse:
- LAZY: charge uniquement quand accédé (recommandé)
- EAGER: charge immédiatement avec l'entité parente
@OneToMany(fetch = FetchType.LAZY) // Défaut pour collections
private List<Order> orders;
@ManyToOne(fetch = FetchType.EAGER) // Défaut pour single
private User user;
Question 15: Qu'est-ce que le problème N+1?
Réponse: Quand on charge N entités puis fait N requêtes supplémentaires pour les relations:
// 1 requête pour les départements
List<Department> deps = repo.findAll();
// N requêtes pour les employés (1 par département)
deps.forEach(d -> d.getEmployees().size());
Solution: JOIN FETCH ou @EntityGraph.
Question 16: Comment résoudre le problème N+1 avec JOIN FETCH?
Réponse:
@Query("SELECT d FROM Department d JOIN FETCH d.employees")
List<Department> findAllWithEmployees();
Une seule requête charge tout.
Question 17: Qu'est-ce qu'un @EntityGraph?
Réponse: Définit quelles relations charger:
@EntityGraph(attributePaths = {"employees", "manager"})
List<Department> findAll();
// Ou défini sur l'entité
@NamedEntityGraph(
name = "Department.full",
attributeNodes = @NamedAttributeNode("employees")
)
@Entity
public class Department { }
@EntityGraph("Department.full")
List<Department> findByName(String name);
Question 18: Comment gérer les cascades?
Réponse:
@OneToMany(cascade = CascadeType.ALL) // Toutes les opérations
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
// Types:
// PERSIST - sauvegarder
// MERGE - mettre à jour
// REMOVE - supprimer
// REFRESH - rafraîchir
// DETACH - détacher
// ALL - tout
Question 19: Qu'est-ce que orphanRemoval?
Réponse: Supprime automatiquement les enfants orphelins:
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children;
// Si on retire un enfant de la liste, il est supprimé de la DB
parent.getChildren().remove(child); // DELETE en DB
Question 20: Comment éviter les références circulaires en JSON?
Réponse:
// Option 1: @JsonIgnore
@JsonIgnore
@ManyToOne
private Department department;
// Option 2: @JsonManagedReference / @JsonBackReference
@JsonManagedReference
@OneToMany(mappedBy = "department")
private List<Employee> employees;
@JsonBackReference
@ManyToOne
private Department department;
// Option 3: Utiliser des DTOs (recommandé)
Questions 21-30: Pagination et Performance
Question 21: Comment implémenter la pagination?
Réponse:
// 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);
// Résultat
page.getContent(); // Liste
page.getTotalElements(); // Total
page.getTotalPages(); // Nombre de pages
page.getNumber(); // Page actuelle
Question 22: Comment trier les résultats?
Réponse:
// Par méthode
List<User> findByActiveOrderByNameAsc(boolean active);
// Par paramètre Sort
List<User> findByActive(boolean active, Sort sort);
userRepository.findByActive(true, Sort.by("name").descending());
// Multi-critères
Sort sort = Sort.by("lastName").and(Sort.by("firstName"));
Question 23: Comment utiliser Slice au lieu de Page?
Réponse: Slice ne compte pas le total (plus performant):
Slice<User> findByActiveTrue(Pageable pageable);
// Résultat
slice.getContent();
slice.hasNext(); // Y a-t-il une page suivante?
slice.hasPrevious();
// Pas de getTotalElements() ni getTotalPages()
Question 24: Comment auditer les entités (createdAt, updatedAt)?
Réponse:
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class Auditable {
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
private String createdBy;
}
// Activer
@EnableJpaAuditing
@Configuration
public class JpaConfig { }
Question 25: Comment implémenter le soft delete?
Réponse:
@Entity
@Where(clause = "deleted = false") // Hibernate
public class User {
private boolean deleted = false;
}
// Ou avec @SQLDelete
@SQLDelete(sql = "UPDATE users SET deleted = true WHERE id = ?")
Question 26: Comment optimiser les requêtes avec des index?
Réponse:
@Entity
@Table(indexes = {
@Index(name = "idx_email", columnList = "email"),
@Index(name = "idx_name_dept", columnList = "lastName, department")
})
public class User { }
Question 27: Qu'est-ce que le cache de second niveau?
Réponse: Cache partagé entre sessions Hibernate:
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product { }
// Configuration
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=...
Question 28: Comment utiliser les Specifications pour des requêtes dynamiques?
Réponse:
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"));
}
// Combinaison
userRepository.findAll(hasName("John").and(isActive()));
Question 29: Comment exécuter des requêtes en batch?
Réponse:
// Configuration
spring.jpa.properties.hibernate.jdbc.batch_size=50
// Code
@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();
}
}
}
Question 30: Comment gérer les versions pour l'optimistic locking?
Réponse:
@Entity
public class Product {
@Version
private Long version;
}
// Si deux transactions modifient simultanément,
// une lève OptimisticLockException
Score
| Score | Niveau |
|---|---|
| 27-30 | Excellent - JPA maîtrisé |
| 21-26 | Bien - Quelques révisions |
| 15-20 | Moyen - Relisez les chapitres |
| Moins de 15 | À revoir |