Approche moderne d'un projet IBM i
Approche moderne d’un projet IBM i
Structure de données vraiment simpliste (Zen… ça vient après)
Structure des modules RPG (couches)
A. Couche DAO (Data Access Objects) - Accès aux données
FOURNISSEUR_DAO - CRUD pour FOURNISSEUR
PRODUIT_DAO - CRUD pour PRODUIT
CLIENT_DAO - CRUD pour CLIENT
COMMANDE_DAO - CRUD pour COMMANDE
LIGNE_CMD_DAO - CRUD pour LIGNE_COMMANDE
B. Couche Métier (Business Logic)
COMMANDE_BL - Logique métier commandes (validation, calculs totaux, etc.)
PRODUIT_BL - Logique métier produits (vérification stock, prix, etc.)
C. Couche Service (exposition REST API) … ou bon vieux DSPF… (...soupirs…)
Services REST pour chaque entité
Format JSON pour les échanges
DSPF ( 😹)
Exemple concret : Module DAO pour FOURNISSEUR
Comment structurer un module DAO complet en RPG Free-Form ? Exemple pour la table FOURNISSEUR avec :
Procédure Fournisseur_Create()
Procédure Fournisseur_Read()
Procédure Fournisseur_Update()
Procédure Fournisseur_Delete() (logique)
Procédure Fournisseur_List() (avec filtres)
Chaque module inclut :
Opérations CRUD complètes (Create, Read, Update, Delete)
Gestion des curseurs SQL pour les listings
Procédures utilitaires (Exists, Get, Count)
Support des filtres et paramètres optionnels
Suppression logique (FOURNISSEUR, PRODUIT, CLIENT)
SQL pur DB2 for i (pas de DDS… NAAAAAAAAAAAAAN !!!! SURTOUT PAS !!!!!!!!!!!!!!!!)
**FREE
//==============================================================================
// Module: FOURNISS_DAO (Data Access Object pour FOURNISSEUR)
// Description: Gère toutes les opérations CRUD sur la table FOURNISSEUR
//==============================================================================
ctl-opt nomain thread(*yes);
//------------------------------------------------------------------------------
// Prototypes des procédures exportées
//------------------------------------------------------------------------------
// Créer un nouveau fournisseur
dcl-pr Fournisseur_Create int(10) export;
societe varchar(30) const;
end-pr;
// Lire un fournisseur par son code
dcl-pr Fournisseur_Read ind export;
code int(10) const;
societe varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-pr;
// Mettre à jour un fournisseur
dcl-pr Fournisseur_Update ind export;
code int(10) const;
societe varchar(30) const;
end-pr;
// Supprimer logiquement un fournisseur
dcl-pr Fournisseur_Delete ind export;
code int(10) const;
end-pr;
// Lister les fournisseurs avec filtre optionnel
dcl-pr Fournisseur_List ind export;
filtre varchar(30) const options(*nopass);
supprimesInclus ind const options(*nopass);
end-pr;
// Vérifier si un fournisseur existe et est actif
dcl-pr Fournisseur_Exists ind export;
code int(10) const;
end-pr;
// Obtenir le nom d'un fournisseur
dcl-pr Fournisseur_GetNom varchar(30) export;
code int(10) const;
end-pr;
//------------------------------------------------------------------------------
// Implémentation des procédures
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Créer un nouveau fournisseur
// Retourne: Code du fournisseur créé, ou 0 en cas d'erreur
//------------------------------------------------------------------------------
dcl-proc Fournisseur_Create export;
dcl-pi *n int(10);
societe varchar(30) const;
end-pi;
dcl-s nouveauCode int(10) inz(0);
exec sql
INSERT INTO FOURNISSEUR (SOCIETE)
VALUES (:societe);
if sqlcode = 0;
exec sql
SELECT IDENTITY_VAL_LOCAL()
INTO :nouveauCode
FROM SYSIBM.SYSDUMMY1;
endif;
return nouveauCode;
end-proc;
//------------------------------------------------------------------------------
// Lire un fournisseur par son code
// Retourne: *ON si trouvé, *OFF sinon
//------------------------------------------------------------------------------
dcl-proc Fournisseur_Read export;
dcl-pi *n ind;
code int(10) const;
societe varchar(30);
suppression char(1);
dateCreation date;
dateModification date;
end-pi;
exec sql
SELECT SOCIETE, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
INTO :societe, :suppression, :dateCreation, :dateModification
FROM FOURNISSEUR
WHERE CODE = :code;
return (sqlcode = 0);
end-proc;
//------------------------------------------------------------------------------
// Mettre à jour un fournisseur
// Retourne: *ON si succès, *OFF sinon
//------------------------------------------------------------------------------
dcl-proc Fournisseur_Update export;
dcl-pi *n ind;
code int(10) const;
societe varchar(30) const;
end-pi;
exec sql
UPDATE FOURNISSEUR
SET SOCIETE = :societe,
DATE_MODIFICATION = CURRENT_DATE
WHERE CODE = :code
AND SUPPRESSION = '0';
return (sqlcode = 0 and sqlerrd(3) > 0);
end-proc;
//------------------------------------------------------------------------------
// Supprimer logiquement un fournisseur
// Retourne: *ON si succès, *OFF sinon
//------------------------------------------------------------------------------
dcl-proc Fournisseur_Delete export;
dcl-pi *n ind;
code int(10) const;
end-pi;
exec sql
UPDATE FOURNISSEUR
SET SUPPRESSION = '1',
DATE_MODIFICATION = CURRENT_DATE
WHERE CODE = :code
AND SUPPRESSION = '0';
return (sqlcode = 0 and sqlerrd(3) > 0);
end-proc;
//------------------------------------------------------------------------------
// Lister les fournisseurs
// Utilise un curseur SQL pour parcourir les résultats
// Retourne: *ON si des enregistrements trouvés, *OFF sinon
//------------------------------------------------------------------------------
dcl-proc Fournisseur_List export;
dcl-pi *n ind;
filtre varchar(30) const options(*nopass);
supprimesInclus ind const options(*nopass);
end-pi;
dcl-s filtreLocal varchar(30);
dcl-s inclureSupprimes ind inz(*off);
dcl-s pattern varchar(32);
// Gestion des paramètres optionnels
if %parms() >= 1;
filtreLocal = %trim(filtre);
else;
filtreLocal = '';
endif;
if %parms() >= 2;
inclureSupprimes = supprimesInclus;
endif;
// Construire le pattern LIKE
if filtreLocal <> '';
pattern = '%' + %trim(filtreLocal) + '%';
else;
pattern = '%';
endif;
// Déclarer le curseur
if inclureSupprimes;
exec sql
DECLARE C_FOURNISSEUR CURSOR FOR
SELECT CODE, SOCIETE, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
FROM FOURNISSEUR
WHERE SOCIETE LIKE :pattern
ORDER BY SOCIETE;
else;
exec sql
DECLARE C_FOURNISSEUR CURSOR FOR
SELECT CODE, SOCIETE, SUPPRESSION, DATE_CREATION, DATE_MODIFICATION
FROM FOURNISSEUR
WHERE SOCIETE LIKE :pattern
AND SUPPRESSION = '0'
ORDER BY SOCIETE;
endif;
exec sql OPEN C_FOURNISSEUR;
return (sqlcode = 0);
end-proc;
//------------------------------------------------------------------------------
// Vérifier si un fournisseur existe et est actif
// Retourne: *ON si existe et actif, *OFF sinon
//------------------------------------------------------------------------------
dcl-proc Fournisseur_Exists export;
dcl-pi *n ind;
code int(10) const;
end-pi;
dcl-s compte int(10);
exec sql
SELECT COUNT(*)
INTO :compte
FROM FOURNISSEUR
WHERE CODE = :code
AND SUPPRESSION = '0';
return (compte > 0);
end-proc;
//------------------------------------------------------------------------------
// Obtenir le nom d'un fournisseur
// Retourne: Nom du fournisseur ou chaîne vide si non trouvé
//------------------------------------------------------------------------------
dcl-proc Fournisseur_GetNom export;
dcl-pi *n varchar(30);
code int(10) const;
end-pi;
dcl-s nom varchar(30) inz('');
exec sql
SELECT SOCIETE
INTO :nom
FROM FOURNISSEUR
WHERE CODE = :code
AND SUPPRESSION = '0';
if sqlcode <> 0;
nom = '';
endif;
return nom;
end-proc;
Base de Données
-- ============================================================================
-- NETTOYAGE (si besoin de recommencer)
-- ============================================================================
-- Décommenter ces lignes si vous voulez supprimer les tables existantes
/*
DROP TABLE LIGNE_COMMANDE;
DROP TABLE COMMANDE;
DROP TABLE PRODUIT;
DROP TABLE CLIENT;
DROP TABLE FOURNISSEUR;
*/
-- ============================================================================
-- TABLE: FOURNISSEUR
-- Description: Gestion des fournisseurs
-- Suppression: Logique (flag SUPPRESSION)
-- ============================================================================
CREATE TABLE FOURNISSEUR (
CODE INTEGER NOT NULL
GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1),
SOCIETE VARCHAR(30) NOT NULL,
SUPPRESSION CHAR(1) NOT NULL DEFAULT '0',
DATE_CREATION DATE NOT NULL DEFAULT CURRENT_DATE,
DATE_MODIFICATION DATE NOT NULL DEFAULT CURRENT_DATE,
CONSTRAINT PK_FOURNISSEUR PRIMARY KEY (CODE),
CONSTRAINT CHK_FOURNISSEUR_SUPPRESSION CHECK (SUPPRESSION IN ('0', '1'))
);
LABEL ON TABLE FOURNISSEUR IS 'Fournisseurs';
LABEL ON COLUMN FOURNISSEUR (
CODE IS 'Code fournisseur',
SOCIETE IS 'Nom de la société',
SUPPRESSION IS 'Suppression logique (0=Actif, 1=Supprimé)',
DATE_CREATION IS 'Date de création',
DATE_MODIFICATION IS 'Date de dernière modification'
);
COMMENT ON TABLE FOURNISSEUR IS
'Table des fournisseurs. Suppression logique via flag SUPPRESSION.';
-- ============================================================================
-- TABLE: CLIENT
-- Description: Gestion des clients
-- Suppression: Logique (flag SUPPRESSION)
-- ============================================================================
CREATE TABLE CLIENT (
CODE INTEGER NOT NULL
GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1),
PRENOM VARCHAR(30) NOT NULL,
NOM VARCHAR(30) NOT NULL,
SUPPRESSION CHAR(1) NOT NULL DEFAULT '0',
DATE_CREATION DATE NOT NULL DEFAULT CURRENT_DATE,
DATE_MODIFICATION DATE NOT NULL DEFAULT CURRENT_DATE,
CONSTRAINT PK_CLIENT PRIMARY KEY (CODE),
CONSTRAINT CHK_CLIENT_SUPPRESSION CHECK (SUPPRESSION IN ('0', '1'))
);
LABEL ON TABLE CLIENT IS 'Clients';
LABEL ON COLUMN CLIENT (
CODE IS 'Code client',
PRENOM IS 'Prénom',
NOM IS 'Nom',
SUPPRESSION IS 'Suppression logique (0=Actif, 1=Supprimé)',
DATE_CREATION IS 'Date de création',
DATE_MODIFICATION IS 'Date de dernière modification'
);
COMMENT ON TABLE CLIENT IS
'Table des clients. Suppression logique via flag SUPPRESSION.';
-- ============================================================================
-- TABLE: PRODUIT
-- Description: Catalogue des produits
-- Suppression: Logique (flag SUPPRESSION)
-- Relation: PRODUIT → FOURNISSEUR (obligatoire)
-- ============================================================================
CREATE TABLE PRODUIT (
CODE INTEGER NOT NULL
GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1),
DESCRIPTION VARCHAR(30) NOT NULL,
FOURNISSEUR INTEGER NOT NULL,
PRIX DECIMAL(8,2) NOT NULL DEFAULT 0,
SUPPRESSION CHAR(1) NOT NULL DEFAULT '0',
DATE_CREATION DATE NOT NULL DEFAULT CURRENT_DATE,
DATE_MODIFICATION DATE NOT NULL DEFAULT CURRENT_DATE,
CONSTRAINT PK_PRODUIT PRIMARY KEY (CODE),
CONSTRAINT CHK_PRODUIT_SUPPRESSION CHECK (SUPPRESSION IN ('0', '1')),
CONSTRAINT CHK_PRODUIT_PRIX CHECK (PRIX >= 0));
LABEL ON TABLE PRODUIT IS 'Produits';
LABEL ON COLUMN PRODUIT (
CODE IS 'Code produit',
DESCRIPTION IS 'Description du produit',
FOURNISSEUR IS 'Code fournisseur (FK)',
PRIX IS 'Prix unitaire',
SUPPRESSION IS 'Suppression logique (0=Actif, 1=Supprimé)',
DATE_CREATION IS 'Date de création',
DATE_MODIFICATION IS 'Date de dernière modification');
COMMENT ON TABLE PRODUIT IS
'Catalogue des produits. Suppression logique via flag SUPPRESSION. ' ||
'Lié obligatoirement à un fournisseur.';
-- Clé étrangère vers FOURNISSEUR
-- Pas de CASCADE: on ne peut pas supprimer un fournisseur qui a des produits
ALTER TABLE PRODUIT
ADD CONSTRAINT FK_PRODUIT_FOURNISSEUR
FOREIGN KEY (FOURNISSEUR)
REFERENCES FOURNISSEUR(CODE);
COMMENT ON CONSTRAINT FK_PRODUIT_FOURNISSEUR ON PRODUIT IS
'Lien vers le fournisseur. Pas de CASCADE: utiliser la suppression logique.';
-- ============================================================================
-- TABLE: COMMANDE
-- Description: En-tête des commandes
-- Suppression: Physique (avec CASCADE sur les lignes)
-- Relation: COMMANDE → CLIENT (obligatoire)
-- ============================================================================
CREATE TABLE COMMANDE (
NUMERO INTEGER NOT NULL
GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1),
DATE_COMMANDE DATE NOT NULL DEFAULT CURRENT_DATE,
CLIENT INTEGER NOT NULL,
CONSTRAINT PK_COMMANDE PRIMARY KEY (NUMERO)
);
LABEL ON TABLE COMMANDE IS 'Commandes (en-tête)';
LABEL ON COLUMN COMMANDE (
NUMERO IS 'Numéro de commande',
DATE_COMMANDE IS 'Date de la commande',
CLIENT IS 'Code client (FK)'
);
COMMENT ON TABLE COMMANDE IS
'En-tête des commandes. Suppression physique avec CASCADE sur LIGNE_COMMANDE.';
-- Clé étrangère vers CLIENT
-- Pas de CASCADE: on ne peut pas supprimer un client qui a des commandes
ALTER TABLE COMMANDE
ADD CONSTRAINT FK_COMMANDE_CLIENT
FOREIGN KEY (CLIENT)
REFERENCES CLIENT(CODE);
COMMENT ON CONSTRAINT FK_COMMANDE_CLIENT ON COMMANDE IS
'Lien vers le client. Pas de CASCADE: protège l''historique des commandes.';
-- ============================================================================
-- TABLE: LIGNE_COMMANDE
-- Description: Détail des lignes de commande
-- Suppression: Physique avec CASCADE depuis COMMANDE
-- Relations: LIGNE_COMMANDE → COMMANDE (obligatoire, CASCADE)
-- LIGNE_COMMANDE → PRODUIT (obligatoire)
-- ============================================================================
CREATE TABLE LIGNE_COMMANDE (
NUMERO_COMMANDE INTEGER NOT NULL,
NUMERO_LIGNE INTEGER NOT NULL,
PRODUIT INTEGER NOT NULL,
QUANTITE INTEGER NOT NULL DEFAULT 1,
PRIX DECIMAL(9,2) NOT NULL DEFAULT 0,
CONSTRAINT PK_LIGNE_COMMANDE PRIMARY KEY (NUMERO_COMMANDE, NUMERO_LIGNE),
CONSTRAINT CHK_LIGNE_COMMANDE_QUANTITE CHECK (QUANTITE > 0),
CONSTRAINT CHK_LIGNE_COMMANDE_PRIX CHECK (PRIX >= 0)
);
LABEL ON TABLE LIGNE_COMMANDE IS 'Lignes de commande (détail)';
LABEL ON COLUMN LIGNE_COMMANDE (
NUMERO_COMMANDE IS 'Numéro de commande (FK)',
NUMERO_LIGNE IS 'Numéro de ligne',
PRODUIT IS 'Code produit (FK)',
QUANTITE IS 'Quantité commandée',
PRIX IS 'Prix total de la ligne (quantité × prix unitaire)'
);
COMMENT ON TABLE LIGNE_COMMANDE IS
'Détail des lignes de commande. ' ||
'Supprimées automatiquement (CASCADE) quand la commande est supprimée.';
-- ⭐ Clé étrangère vers COMMANDE avec CASCADE
-- CASCADE: Si on supprime une COMMANDE, ses lignes n'ont plus de sens
ALTER TABLE LIGNE_COMMANDE
ADD CONSTRAINT FK_LIGNE_COMMANDE_COMMANDE
FOREIGN KEY (NUMERO_COMMANDE)
REFERENCES COMMANDE(NUMERO)
ON DELETE CASCADE;
COMMENT ON CONSTRAINT FK_LIGNE_COMMANDE_COMMANDE ON LIGNE_COMMANDE IS
'Lien vers la commande. CASCADE activé: suppression automatique des lignes.';
-- Clé étrangère vers PRODUIT
-- Pas de CASCADE: on ne peut pas supprimer un produit référencé dans des commandes
ALTER TABLE LIGNE_COMMANDE
ADD CONSTRAINT FK_LIGNE_COMMANDE_PRODUIT
FOREIGN KEY (PRODUIT)
REFERENCES PRODUIT(CODE);
COMMENT ON CONSTRAINT FK_LIGNE_COMMANDE_PRODUIT ON LIGNE_COMMANDE IS
'Lien vers le produit. Pas de CASCADE: protège l''historique des commandes.';
-- ============================================================================
-- INDEX POUR AMÉLIORER LES PERFORMANCES
-- ============================================================================
-- Index sur les colonnes de recherche fréquente
CREATE INDEX IDX_FOURNISSEUR_SOCIETE
ON FOURNISSEUR(SOCIETE);
CREATE INDEX IDX_PRODUIT_DESCRIPTION
ON PRODUIT(DESCRIPTION);
CREATE INDEX IDX_PRODUIT_FOURNISSEUR
ON PRODUIT(FOURNISSEUR);
CREATE INDEX IDX_CLIENT_NOM
ON CLIENT(NOM);
CREATE INDEX IDX_CLIENT_PRENOM
ON CLIENT(PRENOM);
CREATE INDEX IDX_COMMANDE_CLIENT
ON COMMANDE(CLIENT);
CREATE INDEX IDX_COMMANDE_DATE
ON COMMANDE(DATE_COMMANDE);
CREATE INDEX IDX_LIGNE_PRODUIT
ON LIGNE_COMMANDE(PRODUIT);
-- ============================================================================
-- VÉRIFICATIONS POST-INSTALLATION
-- ============================================================================
-- Afficher les tables créées
SELECT TABLE_NAME, TABLE_TEXT, TABLE_TYPE
FROM QSYS2.SYSTABLES
WHERE TABLE_SCHEMA = CURRENT_SCHEMA
AND TABLE_TYPE = 'T'
ORDER BY TABLE_NAME;
-- Afficher les contraintes
SELECT TABLE_NAME,
CONSTRAINT_NAME,
CONSTRAINT_TYPE,
DELETE_RULE,
UPDATE_RULE
FROM QSYS2.SYSCST
WHERE CONSTRAINT_SCHEMA = CURRENT_SCHEMA
ORDER BY TABLE_NAME, CONSTRAINT_NAME;
-- Afficher les index
SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAMES
FROM QSYS2.SYSINDEXES
WHERE TABLE_SCHEMA = CURRENT_SCHEMA
ORDER BY TABLE_NAME, INDEX_NAME;
-- ============================================================================
-- TEST DU CASCADE
-- ============================================================================
-- Créer une commande test
INSERT INTO COMMANDE (CLIENT, DATE_COMMANDE)
VALUES (1, CURRENT_DATE);
-- Récupérer le numéro généré (devrait être 4)
VALUES IDENTITY_VAL_LOCAL();
-- Ajouter des lignes à la commande test (numéro 4)
INSERT INTO LIGNE_COMMANDE (NUMERO_COMMANDE, NUMERO_LIGNE, PRODUIT, QUANTITE, PRIX)
VALUES (4, 1, 1, 1, 100.00);
INSERT INTO LIGNE_COMMANDE (NUMERO_COMMANDE, NUMERO_LIGNE, PRODUIT, QUANTITE, PRIX)
VALUES (4, 2, 2, 1, 50.00);
-- Vérifier les lignes
SELECT * FROM LIGNE_COMMANDE WHERE NUMERO_COMMANDE = 4;
-- Devrait afficher 2 lignes
-- Supprimer la commande (les lignes doivent être supprimées automatiquement)
DELETE FROM COMMANDE WHERE NUMERO = 4;
-- Vérifier que les lignes ont disparu
SELECT * FROM LIGNE_COMMANDE WHERE NUMERO_COMMANDE = 4;
-- Devrait afficher 0 ligne (CASCADE fonctionne !)
-- ============================================================================
-- RÉSUMÉ DE L'INTÉGRITÉ RÉFÉRENTIELLE
-- ============================================================================
/*
┌─────────────────────────────────────────────────────────────────────┐
│ SCHÉMA DE LA BASE DE DONNÉES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ FOURNISSEUR (suppression logique) │
│ ↑ │
│ │ FK (NO CASCADE) │
│ │ │
│ PRODUIT (suppression logique) │
│ │
│ │
│ CLIENT (suppression logique) │
│ ↑ │
│ │ FK (NO CASCADE) │
│ │ │
│ COMMANDE (suppression physique) │
│ ↑ │
│ │ FK (CASCADE) │
│ │ │
│ LIGNE_COMMANDE (suppression physique via CASCADE) │
│ │ │
│ │ FK (NO CASCADE) │
│ ↓ │
│ PRODUIT │
│ │
└─────────────────────────────────────────────────────────────────────┘
Règles de suppression:
- FOURNISSEUR → Suppression LOGIQUE (flag SUPPRESSION='1')
- PRODUIT → Suppression LOGIQUE (flag SUPPRESSION='1')
- CLIENT → Suppression LOGIQUE (flag SUPPRESSION='1')
- COMMANDE → Suppression PHYSIQUE (DELETE)
- LIGNE_CMD → Suppression AUTOMATIQUE via CASCADE
Protections:
✅ Impossible de supprimer un FOURNISSEUR qui a des PRODUITS
✅ Impossible de supprimer un CLIENT qui a des COMMANDES
✅ Impossible de supprimer un PRODUIT référencé dans des LIGNE_COMMANDE
⭐ La suppression d'une COMMANDE supprime automatiquement ses LIGNE_COMMANDE
*/
-- ============================================================================
-- FIN DU SCRIPT
-- ============================================================================


Commentaires
Enregistrer un commentaire