
Projections Spring Data
Apprenez à utiliser les projections Spring Data (basées sur interface ou classe) pour ne récupérer que les données nécessaires de vos entités JPA, améliorant performances et design API.
Le besoin de vues partielles : Pourquoi les projections ?
Lorsque nous interrogeons nos bases de données via les repositories Spring Data JPA, nous récupérons souvent des instances complètes de nos entités (par exemple, un objet User avec tous ses champs : id, nom, email, adresse, historique, etc.). Cependant, dans de nombreux cas d'utilisation, nous n'avons besoin que d'un sous-ensemble de ces informations.
Par exemple, pour afficher une simple liste d'utilisateurs, nous pourrions n'avoir besoin que de leur nom d'utilisateur et de leur email. Récupérer l'entité complète depuis la base de données, avec toutes ses colonnes et potentiellement des relations chargées (même en lazy loading), peut être inefficace et gaspiller des ressources (bande passante réseau, mémoire, temps CPU pour le mapping ORM).
Les Projections Spring Data offrent une solution élégante à ce problème. Elles permettent de définir une "vue" spécifique sur vos entités, en ne sélectionnant et en ne retournant que les attributs dont vous avez réellement besoin pour un cas d'usage donné. Cela peut considérablement améliorer les performances et permet également de mieux concevoir les contrats de vos API en n'exposant que les données pertinentes.
Projections basées sur interface (Closed Projections)
La manière la plus simple et la plus courante de définir une projection est d'utiliser une interface Java. Cette interface déclare simplement des méthodes getter pour les propriétés de l'entité que vous souhaitez inclure dans la projection. Les noms des méthodes getter doivent correspondre exactement aux noms des propriétés de l'entité.
Spring Data reconnaît cette interface comme une définition de projection et génère la requête SQL/JPQL optimisée pour ne sélectionner que les colonnes correspondantes aux propriétés définies dans l'interface.
Exemple : Supposons une entité Product :
@Entity
class Product {
@Id @GeneratedValue
private Long id;
private String name;
private String description;
private BigDecimal price;
private String category;
private boolean inStock;
// Getters, Setters...
}
Nous pouvons définir une projection pour n'afficher que le nom et le prix :
// Interface de projection
public interface ProductNameAndPrice {
String getName();
BigDecimal getPrice();
}
Ensuite, dans notre ProductRepository, nous pouvons définir une méthode qui retourne cette projection :
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ProductRepository extends JpaRepository {
// Retourne une liste de projections au lieu d'entités complètes
List findByCategory(String category);
}
Lorsque findByCategory est appelée, Spring Data JPA exécutera une requête optimisée (par exemple, SELECT p.name, p.price FROM Product p WHERE p.category = ?1) et retournera une liste d'instances (générées dynamiquement via proxy) qui implémentent l'interface ProductNameAndPrice.
Ce type de projection est appelé "Closed Projection" car les propriétés sont déterminées strictement par les méthodes getter définies.
Projections basées sur interface (Open Projections)
Les projections basées sur interface peuvent également être "ouvertes" (Open Projections). Elles permettent plus de flexibilité en utilisant l'annotation @Value avec des expressions SpEL (Spring Expression Language) sur les méthodes getter de l'interface de projection.
Cela permet de calculer de nouvelles valeurs, de formater des données ou d'accéder à des propriétés imbriquées de manière personnalisée. La variable SpEL target fait référence à l'instance de l'entité racine complète.
Exemple :
public interface ProductSummary {
String getName();
// Utilise SpEL pour concaténer catégorie et stock
@Value("#{target.category + ' (' + (target.inStock ? 'En stock' : 'Epuisé') + ')'}")
String getCategoryAndStockStatus();
// Accède à une propriété imbriquée (si Product avait une relation 'manufacturer')
// @Value("#{target.manufacturer.name}")
// String getManufacturerName();
}
Utilisation dans le Repository :
public interface ProductRepository extends JpaRepository {
List findByPriceLessThan(BigDecimal maxPrice);
}
Attention : Les Open Projections sont plus puissantes mais peuvent avoir un impact sur les performances. Comme SpEL a besoin d'accéder potentiellement à n'importe quelle propriété de l'entité (via target), Spring Data ne peut pas toujours optimiser la requête SQL/JPQL pour ne sélectionner que les colonnes strictement nécessaires. Il est possible que l'entité complète soit chargée en mémoire avant que SpEL ne soit évalué. Utilisez-les judicieusement.
Projections basées sur classe (DTOs)
Une autre façon de définir des projections est d'utiliser des classes Java classiques, souvent appelées DTOs (Data Transfer Objects).
Pour qu'une classe DTO puisse être utilisée comme projection, elle doit respecter l'une des conditions suivantes :
- Avoir un constructeur dont les noms et types de paramètres correspondent exactement aux propriétés de l'entité que vous souhaitez projeter.
- Avoir des propriétés (avec getters et potentiellement setters) dont les noms et types correspondent aux propriétés de l'entité à projeter. La classe doit avoir un constructeur par défaut accessible.
Spring Data essaiera d'abord de trouver et d'utiliser un constructeur correspondant. S'il n'en trouve pas, il essaiera d'instancier la classe via le constructeur par défaut et de peupler les propriétés correspondantes.
Exemple :
// DTO pour la projection
public class ProductBasicInfoDto {
private final String name;
private final BigDecimal price;
// Constructeur correspondant aux propriétés à sélectionner
public ProductBasicInfoDto(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
// Getters...
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
}
Utilisation dans le Repository :
public interface ProductRepository extends JpaRepository {
// Retourne une liste de DTOs
List findByInStockTrue();
}
Ici, Spring Data JPA générera une requête comme SELECT p.name, p.price FROM Product p WHERE p.inStock = true et utilisera le constructeur de ProductBasicInfoDto pour instancier les objets de la liste retournée.
Les projections basées sur classe offrent une alternative claire aux interfaces, surtout si vous utilisez déjà des DTOs dans d'autres couches de votre application.
Projections dynamiques
Spring Data permet également d'utiliser des projections dynamiques. Au lieu de fixer le type de retour de la méthode du repository, vous pouvez utiliser un type générique et passer le type de projection souhaité en tant que paramètre à la méthode.
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ProductRepository extends JpaRepository {
// Méthode acceptant un type de projection dynamique
List findByNameContainingIgnoreCase(String name, Class type);
}
Vous pouvez ensuite appeler cette méthode en spécifiant l'interface ou la classe de projection que vous voulez :
// Dans un service
@Autowired
ProductRepository productRepository;
public void searchProducts(String keyword) {
// Obtenir une liste de projections Nom/Prix
List namesAndPrices =
productRepository.findByNameContainingIgnoreCase(keyword, ProductNameAndPrice.class);
// Obtenir une liste de DTOs basiques
List basicInfos =
productRepository.findByNameContainingIgnoreCase(keyword, ProductBasicInfoDto.class);
// Obtenir la liste des entités complètes
List fullProducts =
productRepository.findByNameContainingIgnoreCase(keyword, Product.class);
// ... traiter les résultats
}
Les projections dynamiques offrent une grande flexibilité en permettant à une seule méthode de repository de servir différents besoins de représentation des données.
Quand utiliser les projections ?
Les projections sont particulièrement utiles dans les scénarios suivants :
- Optimisation des performances : Lorsque vous n'avez besoin que de quelques champs d'une entité potentiellement large ou avec des relations coûteuses à charger. La réduction de la quantité de données transférées depuis la base peut améliorer significativement les temps de réponse.
- Conception d'API : Pour les API REST, retourner des projections (souvent via des DTOs) permet de définir précisément le contrat de l'API et de n'exposer que les informations nécessaires, améliorant la sécurité et la clarté.
- Vues spécifiques : Pour alimenter des composants d'interface utilisateur spécifiques qui ne nécessitent qu'une vue partielle des données (listes, tableaux de bord, menus déroulants).
En résumé, les projections Spring Data sont un outil puissant et flexible pour optimiser la récupération de données et adapter la forme des données retournées à des cas d'usage spécifiques, améliorant à la fois les performances et la conception de vos applications Spring Data JPA.