Conception d’un système de requêtes dynamiques pour tables SQL en Java
Dans les applications modernes, la capacité à générer des requêtes SQL dynamiquement selon des critères variés est essentielle. Ce mécanisme permet une flexibilité accrue dans l’interrogasion des données, sans nécessiter de modifications manuelles du code pour chaque type de filtre.
Ce guide explique comment concevoir un tel système en utilisant Java, avec une architecture claire et sécurisée contre les injections SQL.
Architecture fondamentale Le processus repose sur plusieurs étapes :
- Modélisation de la structure de base de données.
- Mise en place d’une entité Java correspondante.
- Réception des paramètres de recherche via une couche API ou front-end.
- Construction dynamique de la requête SQL en fonction des critères fournis.
- Exécution sécurisée de la requête et retour des résultats.
Exemple pratique : Recherche d'utilisateurs
1. Structure de la table utilisateur
CREATE TABLE Utilisateur (
id INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(50),
courriel VARCHAR(50),
age INT
);
2. Classe Java représentant l'entité
package com.example.model;
public class Utilisateur {
private int identifiant;
private String nom;
private String courriel;
private int age;
// Accesseurs (getters) et mutateurs (setters)
public int getIdentifiant() { return identifiant; }
public void setIdentifiant(int identifiant) { this.identifiant = identifiant; }
public String getNom() { return nom; }
public void setNom(String nom) { this.nom = nom; }
public String getCourriel() { return courriel; }
public void setCourriel(String courriel) { this.courriel = courriel; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
3. DTO de requête dynamique
package com.example.dto;
public class FiltreUtilisateur {
private String nomPartiel;
private String courrielPartiel;
private Integer ageMinimum;
private Integer ageMaximum;
// Getters et setters
public String getNomPartiel() { return nomPartiel; }
public void setNomPartiel(String nomPartiel) { this.nomPartiel = nomPartiel; }
public String getCourrielPartiel() { return courrielPartiel; }
public void setCourrielPartiel(String courrielPartiel) { this.courrielPartiel = courrielPartiel; }
public Integer getAgeMinimum() { return ageMinimum; }
public void setAgeMinimum(Integer ageMinimum) { this.ageMinimum = ageMinimum; }
public Integer getAgeMaximum() { return ageMaximum; }
public void setAgeMaximum(Integer ageMaximum) { this.ageMaximum = ageMaximum; }
}
4. Service de requête dynamique
package com.example.service;
import com.example.dto.FiltreUtilisateur;
import com.example.model.Utilisateur;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class ServiceUtilisateur {
private Connection obtenirConnexion() throws Exception {
String url = "jdbc:mysql://localhost:3306/monapp";
String user = "admin";
String pwd = "securepass";
return DriverManager.getConnection(url, user, pwd);
}
public List<Utilisateur> rechercher(FiltreUtilisateur filtres) {
List<Utilisateur> resultat = new ArrayList<>();
StringBuilder requete = new StringBuilder("SELECT * FROM Utilisateur WHERE 1=1");
if (filtres.getNomPartiel() != null && !filtres.getNomPartiel().isEmpty()) {
requete.append(" AND nom LIKE ?");
}
if (filtres.getCourrielPartiel() != null && !filtres.getCourrielPartiel().isEmpty()) {
requete.append(" AND courriel LIKE ?");
}
if (filtres.getAgeMinimum() != null) {
requete.append(" AND age >= ?");
}
if (filtres.getAgeMaximum() != null) {
requete.append(" AND age <= ?");
}
try (Connection connexion = obtenirConnexion();
PreparedStatement instruction = connexion.prepareStatement(requete.toString())) {
int indice = 1;
if (filtres.getNomPartiel() != null && !filtres.getNomPartiel().isEmpty()) {
instruction.setString(indice++, "%" + filtres.getNomPartiel() + "%");
}
if (filtres.getCourrielPartiel() != null && !filtres.getCourrielPartiel().isEmpty()) {
instruction.setString(indice++, "%" + filtres.getCourrielPartiel() + "%");
}
if (filtres.getAgeMinimum() != null) {
instruction.setInt(indice++, filtres.getAgeMinimum());
}
if (filtres.getAgeMaximum() != null) {
instruction.setInt(indice++, filtres.getAgeMaximum());
}
ResultSet resultatRs = instruction.executeQuery();
while (resultatRs.next()) {
Utilisateur user = new Utilisateur();
user.setIdentifiant(resultatRs.getInt("id"));
user.setNom(resultatRs.getString("nom"));
user.setCourriel(resultatRs.getString("courriel"));
user.setAge(resultatRs.getInt("age"));
resultat.add(user);
}
} catch (Exception e) {
e.printStackTrace();
}
return resultat;
}
}
5. Contrôleur d’interaction
package com.example.controller;
import com.example.dto.FiltreUtilisateur;
import com.example.service.ServiceUtilisateur;
import java.util.List;
public class ControleurUtilisateur {
private final ServiceUtilisateur service = new ServiceUtilisateur();
public void traiterRequete(FiltreUtilisateur filtres) {
List<Utilisateur> resultats = service.rechercher(filtres);
resultats.forEach(u -> System.out.println(
"ID: " + u.getIdentifiant() +
", Nom: " + u.getNom() +
", Email: " + u.getCourriel() +
", Âge: " + u.getAge()
));
}
public static void main(String[] args) {
ControleurUtilisateur controleur = new ControleurUtilisateur();
FiltreUtilisateur filtres = new FiltreUtilisateur();
filtres.setNomPartiel("Jean");
filtres.setAgeMinimum(30);
filtres.setAgeMaximum(50);
controleur.traiterRequete(filtres);
}
}
Cette approche garantit une construction de requête sécurisée, évitant les failles d'injection SQL grâce à l'utilisation de PreparedStatement. Elle offre également une grande extensibilité pour intégrer de nouveaux filtres sans refonte complète du code.