Génération automatisée de modèles persistants à partir des métadonnées relationnelles

L'analyse des schémas de bases de données offre une base solide pour l'ingénierie logicielle déclarative. En imposant une convention de préfixage systématique sur les noms de colonnes (notamment int_, str_, dtm_, dec_), il devient possible d'extraire automatiquement les métadonnées et de produire des couches de données cohérentes. Cette approche réduit considérablement la quantité de code redondant tout en garantissant une synchronisation étroite entre le stockage relationnel et les objets applicatifs.

Implémentation VB.NET

Le modèle suit une hiérarchie héritée où chaque entité gère son état interne et sa sérialisation vers le système relationnel.

Namespace Persistance.Modeles
    Public Class ClientEntite
        Inherits ObjetBase

        Private Const NomTable As String = "tbl_client"

        Public Shared Function ChercherUn(ByVal pId As Integer) As ClientEntite
            Dim instance As New ClientEntite With {.Identifiant = pId}
            instance.Charger()
            Return instance
        End Function

#Region "Champs Propriétés"
        Public Property Identifiant As Integer = 0
        Public Property Nom As String = String.Empty
        Public Property DateNaissance As DateTime = Date.MinValue
        Public Property Age As Integer = 0
        Public Property Solde As Decimal = 0D
        Public Property Annotation As String = String.Empty
#End Region

#Region "Logique Métier"
        Private Sub AppliquerChargement(row As DataRow, cibles As ClientEntite)
            With cibles
                .Identifiant = If(row.IsNull("int_Id"), 0, row.Item("int_Id"))
                .Nom = If(row.IsNull("str_Nom"), String.Empty, row.Item("str_Nom").ToString())
                .DateNaissance = If(row.IsNull("dtm_Naissance"), DateTime.MinValue, CType(row.Item("dtm_Naissance"), DateTime))
                .Age = If(row.IsNull("int_Age"), 0, row.Item("int_Age"))
                .Solde = If(row.IsNull("dec_Solde"), 0D, row.Item("dec_Solde"))
                .Annotation = If(row.IsNull("str_Annotation"), String.Empty, row.Item("str_Annotation").ToString())
                .EtatEnregistrement = EtatEntite.AucunChangement
            End With
        End Sub

        Public Overrides Function Charger(Optional ByVal donnees As DataTable = Nothing) As List(Of ClientEntite)
            MyBase.Charger()
            Dim resultat As New List(Of ClientEntite)
            Dim origineSGBD As Boolean = False

            If donnees Is Nothing Then
                Using req As IDbCommand = Connecteur.CreerRequête()
                    req.CommandText = "SELECT * FROM tbl_client WHERE int_Id=@pId"
                    Connecteur.AjouterParamètre(req, "@pId", Identifiant)
                    donnees = Connecteur.ExécuterDataTable(req)
                End Using
                origineSGBD = True
            End If

            For Each ligne As DataRow In donnees.Rows
                Dim cible As ClientEntite = If(origineSGBD, Me, New ClientEntite)
                AppliquerChargement(ligne, cible)
                resultat.Add(cible)
            Next

            Return resultat
        End Function

        Public Function VersListe(ByVal tableSource As DataTable) As IEnumerable(Of ObjetBase)
            Dim collection As New List(Of ClientEntite)()
            If tableSource IsNot Nothing Then
                For Each ligne As DataRow In tableSource.Rows
                    Dim obj As New ClientEntite
                    AppliquerChargement(ligne, obj)
                    collection.Add(obj)
                Next
            End If
            Return collection
        End Function

        Public Overrides Function Persistre() As Integer
            MyBase.Persistre()
            Dim cmd As IDbCommand = Connecteur.CreerRequête()

            Select Case Me.EtatEnregistrement
                Case EtatEntite.Nouveau
                    cmd.CommandText = "INSERT INTO tbl_client (str_Nom,dtm_Naissance,int_Age,dec_Solde,str_Annotation) VALUES (@pNom,@pDate,@pAge,@pSolde,@pNote)"
                    Connecteur.AjouterParamètre(cmd, "@pNom", Nom)
                    Connecteur.AjouterParamètre(cmd, "@pDate", DateNaissance)
                    Connecteur.AjouterParamètre(cmd, "@pAge", Age)
                    Connecteur.AjouterParamètre(cmd, "@pSolde", Solde)
                    Connecteur.AjouterParamètre(cmd, "@pNote", Annotation)
                    Connecteur.ExecuterSansResultat(cmd)
                    Identifiant = Connecteur.RécupérerIdentifiant("tbl_client", "int_Id")
                    Me.EtatEnregistrement = EtatEntite.AucunChangement

                Case EtatEntite.Modifié
                    cmd.CommandText = "UPDATE tbl_client SET str_Nom=@pNom, dtm_Naissance=@pDate, int_Age=@pAge, dec_Solde=@pSolde, str_Annotation=@pNote WHERE int_Id=@pId"
                    Connecteur.AjouterParamètre(cmd, "@pNom", Nom)
                    Connecteur.AjouterParamètre(cmd, "@pDate", DateNaissance)
                    Connecteur.AjouterParamètre(cmd, "@pAge", Age)
                    Connecteur.AjouterParamètre(cmd, "@pSolde", Solde)
                    Connecteur.AjouterParamètre(cmd, "@pNote", Annotation)
                    Connecteur.AjouterParamètre(cmd, "@pId", Identifiant)
                    Connecteur.ExecuterSansResultat(cmd)
                    Me.EtatEnregistrement = EtatEntite.AucunChangement

                Case EtatEntite.Supprimé
                    cmd.CommandText = "DELETE FROM tbl_client WHERE int_Id=@pId"
                    Connecteur.AjouterParamètre(cmd, "@pId", Identifiant)
                    Connecteur.ExecuterSansResultat(cmd)
            End Select

            cmd.Dispose()
            Return 0
        End Function
#End Region

    End Class
End Namespace

Modèle Android Java (SQLite)

Sous l'environnement mobile, l'extraction de curseurs et la manipulation de ContentValues sont centralisées dans des méthodes utilitaires dédiées au transfert d'état.

package io.app.modeles;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.ContentValues;
import android.database.Cursor;
import io.app.base.BaseModele;
import io.app.enums.EtatModele;
import io.app.utils.FormatteurDate;

public class ModeleClient extends BaseModele {

    public static final String NOM_TABLE = "tbl_client";

    private int identifiant = 0;
    private String nom = "";
    private Date dateNaissance = FormatteurDate.getDateMinimale();
    private int age = 0;
    private BigDecimal solde = new BigDecimal(0);
    private String annotation = "";

    public ModeleClient() { super(); }

    // Accès mutateurs avec détection de changement
    public int getIdentifiant() { return identifiant; }
    public void setIdentifiant(int val) {
        if (this.identifiant != val) { this.identifiant = val; notifierModification(); }
    }

    public String getNom() { return nom; }
    public void setNom(String val) {
        if (!nom.equalsIgnoreCase(val)) { this.nom = val; notifierModification(); }
    }

    public Date getDateNaissance() { return dateNaissance; }
    public void setDateNaissance(Date val) {
        if (val != null && dateNaissance.getTime() != val.getTime()) { this.dateNaissance = val; notifierModification(); }
    }

    public int getAge() { return age; }
    public void setAge(int val) {
        if (age != val) { age = val; notifierModification(); }
    }

    public BigDecimal getSolde() { return solde; }
    public void setSolde(BigDecimal val) {
        if (!solde.compareTo(val) == 0) { solde = val; notifierModification(); }
    }

    public String getAnnotation() { return annotation; }
    public void setAnnotation(String val) {
        if (!annotation.equalsIgnoreCase(val)) { annotation = val; notifierModification(); }
    }

    private ContentValues construireValuers() {
        ContentValues valeurs = new ContentValues();
        valeurs.put("int_Id", identifiant);
        valeurs.put("str_Nom", nom);
        valeurs.put("dtm_Naissance", FormatteurDate.formater(dateNaissance));
        valeurs.put("int_Age", age);
        valeurs.put("dec_Solde", solde);
        valeurs.put("str_Annotation", annotation);
        return valeurs;
    }

    private ModeleClient mapperCurseur(Cursor cur) {
        ModeleClient instance = new ModeleClient();
        instance.setIdentifiant(cur.getInt(cur.getColumnIndex("int_Id")));
        instance.setNom(cur.getString(cur.getColumnIndex("str_Nom")));
        instance.setDateNaissance(FormatteurDate.parse(cur.getString(cur.getColumnIndex("dtm_Naissance"))));
        instance.setAge(cur.getInt(cur.getColumnIndex("int_Age")));
        instance.setAnnotation(cur.getString(cur.getColumnIndex("str_Annotation")));
        instance.setSolde(new BigDecimal(cur.getDouble(cur.getColumnIndex("dec_Solde"))));
        instance.setEtatModele(EtatModele.AucunChangement);
        return instance;
    }

    @Override
    public void sauvegarder() {
        ContentValues cvs;
        switch (getEtatModele()) {
            case NOUVEAU_ENREGISTREMENT:
                cvs = construireValuers();
                cvs.remove("int_Id");
                GestionnaireBdd.insérer(NOM_TABLE, cvs);
                identifiant = GestionnaireBdd.obtenirIdentifiant(NOM_TABLE);
                setEtatModele(EtatModele.AucunChangement);
                break;
            case MODIFIÉ:
                cvs = construireValuers();
                GestionnaireBdd.modifier(NOM_TABLE, cvs, "int_Id=?", new String[]{String.valueOf(identifiant)});
                setEtatModele(EtatModele.AucunChangement);
                break;
            case SUPPRIMÉ:
                GestionnaireBdd.supprimer(NOM_TABLE, "int_Id=?", new String[]{String.valueOf(identifiant)});
                break;
            default: break;
        }
    }

    public List<modeleclient> récupérerTous() {
        List<modeleclient> liste = new ArrayList<>();
        Cursor cur = GestionnaireBdd.interroger("SELECT * FROM " + NOM_TABLE);
        while (cur.moveToNext()) {
            liste.add(mapperCurseur(cur));
        }
        cur.close();
        return liste;
    }

    public ModeleClient obtenirInstance() {
        return obtenirParId(identifiant);
    }

    public ModeleClient obtenirParId(int pId) {
        ModeleClient résultat = null;
        String requête = String.format("SELECT * FROM %s WHERE int_Id=?", NOM_TABLE);
        Cursor cur = GestionnaireBdd.interroger(requête, new String[]{String.valueOf(pId)});
        if (cur.moveToFirst()) {
            résultat = mapperCurseur(cur);
        }
        cur.close();
        return résultat;
    }
}
</modeleclient></modeleclient>

Persistence Node.js / Express

Côté serveur, la génération s'apuie sur des contrôleurs asynchrones qui traduisent les intentions d'interface en requêtes paramétrées sécurisées.

// Routing principal
const express = require('express');
const router = express.Router();
const { exécuterOpérationClient } = require('./gestionnaires/dbClient');

router.post('/client', async (req, res, next) => {
    try {
        const actionDemandée = req.body.action || "Rechercher";
        const résultats = await exécuterOpérationClient(actionDemandée, req.body);
        res.json(résultats);
    } catch (erreur) {
        res.status(500).json({ erreur: "Erreur système", détails: erreur.message });
    }
});

module.exports = router;


// Couche DAL (Data Access Layer)
const poolBdd = require('./config/poolConnexion');

async function exécuterOpérationClient(action, données) {
    let requêteSQL = "";
    let paramètres = [];
    let contexte = "";

    switch (action.toLowerCase()) {
        case "ajouter":
            requêteSQL = "INSERT INTO tbl_client (str_Nom, dtm_Naissance, int_Age, dec_Solde, str_Annotation) VALUES (?, ?, ?, ?, ?)";
            paramètres = [données.strNom, données.dtmNaissance, données.intAge, données.decSolde, données.strAnnotation];
            contexte = "Insertion client";
            break;

        case "modifier":
            requêteSQL = "UPDATE tbl_client SET str_Nom=?, dtm_Naissance=?, int_Age=?, dec_Solde=?, str_Annotation=? WHERE int_Id=?";
            paramètres = [données.strNom, données.dtmNaissance, données.intAge, données.decSolde, données.strAnnotation, données.intId];
            contexte = "Mise à jour client";
            break;

        case "supprimer":
            requêteSQL = "DELETE FROM tbl_client WHERE int_Id=?";
            paramètres = [données.intId];
            contexte = "Suppression client";
            break;

        default:
            requêteSQL = "SELECT * FROM tbl_client";
            paramètres = [];
            contexte = "Lecture globale";
            break;
    }

    return new Promise((résoudre, rejeter) => {
        poolBdd.query(requêteSQL, paramètres, (err, lignes) => {
            if (err) return rejeter(new Error(contexte + " échoué : " + err.message));
            résoudre({ succès: true, opération: contexte, données: lignes });
        });
    });
}

module.exports = { exécuterOpérationClient };

Ces architectures démontrant comment l'introspection de schéma pilote la création de squelettes de persistance peuvent être étendues à d'autres paradigmes ou extensions de tables sans modification manuelle des flux de travail existants.

Étiquettes: Schema-Driven Development Data Access Object VB.NET Persistence Android SQLite Mapping Node.js Async DAL

Publié le 26 octobre à 07h33