
Création de repositories JDBC
Apprenez à créer des repositories avec Spring Data JDBC dans Spring Boot. Découvrez une alternative à JPA pour un mapping simple et un contrôle SQL direct via @Query.
Créer des repositories JDBC : approche et configuration
Spring Data JDBC offre une alternative intéressante à Spring Data JPA pour interagir avec des bases de données relationnelles. Contrairement à JPA, qui est une spécification ORM (Object-Relational Mapping) complète avec des fonctionnalités avancées comme le suivi des modifications (dirty checking), le chargement paresseux (lazy loading) et un langage de requête portable (JPQL), Spring Data JDBC adopte une approche plus simple et plus directe. Il se concentre sur le mapping des résultats de requêtes SQL vers des objets Java, sans la complexité d'un cache de premier niveau ou d'un suivi automatique des états d'entité. Il est souvent choisi pour sa simplicité, son contrôle plus direct sur le SQL exécuté, et lorsqu'un ORM complet est jugé trop complexe pour les besoins du projet.
Pour commencer avec Spring Data JDBC, la première étape est d'inclure le starter approprié dans votre projet Spring Boot : spring-boot-starter-data-jdbc. Ce starter apportera les dépendances nécessaires, y compris Spring Data JDBC lui-même et généralement HikariCP pour le pooling de connexions. Tout comme avec JPA, le concept central reste la définition d'une interface Repository. Vous créerez une interface qui étend l'une des interfaces de base fournies, comme CrudRepository ou ListCrudRepository (qui retourne des List au lieu d'Iterable), en spécifiant votre type d'entité (T) et le type de son identifiant (ID).
Le mapping des entités en Spring Data JDBC est volontairement plus simple que celui de JPA. Vous utiliserez principalement l'annotation @Id pour marquer la clé primaire et, si nécessaire, @Table("nom_table") ou @Column("nom_colonne") pour spécifier des noms différents de ceux dérivés par convention. Les annotations de relations complexes comme @OneToMany ou @ManyToMany de JPA ne sont pas utilisées de la même manière ; Spring Data JDBC gère les relations de manière plus explicite, souvent via des requêtes dédiées ou l'annotation @MappedCollection pour des cas simples.
package com.myapp.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
// Pas d'import jakarta.persistence.*
@Table("products") // Optionnel si le nom de la table correspond au nom de classe (insensible à la casse)
public class Product {
@Id // Annotation de Spring Data, pas de JPA
private Long id;
private String name;
private String sku;
private Double price;
// Constructeurs, Getters et Setters...
// Un constructeur avec tous les arguments est souvent utile
public Product(Long id, String name, String sku, Double price) {
this.id = id;
this.name = name;
this.sku = sku;
this.price = price;
}
// ... autres méthodes
}
package com.myapp.repository;
import com.myapp.model.Product;
import org.springframework.data.repository.ListCrudRepository;
// Utilisation de ListCrudRepository pour obtenir des List directement
public interface ProductJdbcRepository extends ListCrudRepository {
// Des méthodes de requête dérivées simples sont supportées
List findByName(String name);
List findBySkuContainingIgnoreCase(String skuPart);
}
Comme avec JPA, Spring Data JDBC fournira une implémentation pour cette interface au démarrage, incluant les méthodes CRUD et celles dérivées du nom.
Opérations CRUD et particularités de Spring Data JDBC
Les méthodes CRUD de base comme findById, findAll, deleteById, count, etc., fonctionnent de manière intuitive et similaire à ce que l'on trouve dans Spring Data JPA. Elles exécutent les instructions SQL correspondantes (SELECT, DELETE, COUNT) et mappent les résultats vers vos objets entité.
Cependant, le comportement de la méthode save(T entity) présente une différence clé par rapport à JPA. Dans Spring Data JDBC, la méthode save effectue soit un INSERT, soit un UPDATE, en se basant principalement sur la valeur de l'identifiant (le champ annoté @Id). Si l'ID est null (ou 0 pour les types primitifs numériques), Spring Data JDBC suppose qu'il s'agit d'une nouvelle entité et effectue un INSERT. Si l'ID a une valeur non nulle, il suppose que l'entité existe déjà et tente un UPDATE. Ce comportement peut être affiné en faisant implémenter l'interface Persistable par votre entité, ce qui vous permet de contrôler explicitement si une entité est considérée comme nouvelle via la méthode isNew().
Une autre différence majeure réside dans le suivi des modifications. Spring Data JDBC ne maintient pas d'état pour les entités chargées. Contrairement à JPA où modifier un attribut d'une entité managée dans une transaction peut automatiquement déclencher un UPDATE lors du flush/commit, avec Spring Data JDBC, vous devez explicitement appeler la méthode save() après avoir modifié un objet pour que les changements soient persistés via une instruction UPDATE.
// Exemple de mise à jour explicite
Optional optionalProduct = productJdbcRepository.findById(productId);
if (optionalProduct.isPresent()) {
Product productToUpdate = optionalProduct.get();
// Modifier l'objet récupéré
productToUpdate.setPrice(newPrice);
// Il faut explicitement sauvegarder pour déclencher l'UPDATE !
productJdbcRepository.save(productToUpdate);
}
Concernant les relations entre entités (par exemple, un client ayant plusieurs commandes), Spring Data JDBC ne charge pas automatiquement les entités liées comme le ferait JPA avec @OneToMany (en mode EAGER ou LAZY). Vous devez généralement charger les relations explicitement via des appels séparés au repository concerné ou en utilisant des requêtes personnalisées. L'annotation @MappedCollection peut aider à mapper des collections simples (comme une liste d'IDs ou d'objets simples liés par une clé étrangère) mais elle n'offre pas la richesse des options de mapping et de chargement de JPA.
Requêtes SQL personnalisées via l'annotation @Query
Bien que Spring Data JDBC supporte les requêtes dérivées simples (Query Methods), il est très courant d'avoir besoin de requêtes plus spécifiques ou plus complexes. Pour cela, comme dans les autres modules Spring Data, on utilise l'annotation @Query. La différence fondamentale ici est que la valeur de l'annotation @Query dans Spring Data JDBC doit contenir du SQL natif, et non du JPQL.
Vous pouvez utiliser des paramètres nommés (préfixés par :) dans votre requête SQL. Ces paramètres sont liés aux arguments de la méthode du repository portant le même nom, et ces arguments doivent être annotés avec @Param("nomDuParam") de org.springframework.data.repository.query.Param.
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.query.Param;
public interface ProductJdbcRepository extends ListCrudRepository {
// Requête SELECT personnalisée avec paramètre nommé
@Query("SELECT id, name, sku, price FROM products WHERE price > :minPrice ORDER BY price DESC")
List findProductsCheaperThan(@Param("minPrice") Double minimumPrice);
// Requête retournant une valeur simple
@Query("SELECT count(*) FROM products WHERE name LIKE :namePattern")
int countProductsWithNameLike(@Param("namePattern") String pattern);
}
Il est crucial de lister explicitement les colonnes dans votre SELECT pour que Spring Data JDBC sache comment les mapper aux propriétés de votre entité. Utiliser SELECT * est possible mais moins recommandé car moins explicite et potentiellement moins performant.
L'annotation @Query peut également être utilisée pour des opérations de modification (INSERT, UPDATE, DELETE). Dans ce cas, la méthode du repository doit être annotée avec @Modifying de org.springframework.data.jdbc.repository.query.Modifying et retourne généralement void ou int (représentant le nombre de lignes affectées).
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.query.Param;
public interface ProductJdbcRepository extends ListCrudRepository {
// ... autres méthodes
@Modifying
@Query("UPDATE products SET price = :newPrice WHERE id = :productId")
int updatePrice(@Param("productId") Long id, @Param("newPrice") Double price);
@Modifying
@Query("DELETE FROM products WHERE sku = :skuToDelete")
int deleteBySku(@Param("skuToDelete") String sku);
}
Synthèse : choisir Spring Data JDBC pour la simplicité et le contrôle
La création de repositories avec Spring Data JDBC suit les principes généraux de Spring Data, en fournissant une abstraction sur le code JDBC répétitif et en offrant les opérations CRUD de base ainsi que la possibilité de requêtes dérivées et personnalisées via @Query. Ses principaux avantages résident dans sa simplicité conceptuelle par rapport à un ORM complet comme JPA, son mapping objet-SQL plus direct et le contrôle total sur le SQL exécuté.
Les compromis incluent l'absence de suivi automatique des modifications (nécessitant des appels explicites à save pour les mises à jour), une gestion des relations moins automatisée (souvent manuelle ou via @MappedCollection), et l'absence de JPQL (uniquement SQL natif dans @Query). Il n'y a pas non plus de concepts comme le chargement paresseux (lazy loading) ou le cache de premier niveau propres à JPA.
En conclusion, Spring Data JDBC est une excellente option lorsque vous préférez travailler directement avec SQL, que votre modèle de domaine est relativement simple, ou que vous souhaitez éviter la complexité et l'overhead potentiel d'un ORM complet, tout en bénéficiant de la productivité apportée par l'abstraction Repository de Spring Data pour les opérations courantes.