Notes

ew

2023

Mainframe

Ordinateur centralisé qui partage ses ressources entre les utilisateurs. Dans le cas de cobol: un OS appelé z/OS (ou MVS). Origine années 60 , un processeur avec typiquement de nombreux disques mémoire (ROM/RAM)

Objectif d’un mainframe -> typiquement un point d’accès et de dispatch sur un data center.

overview

.____________________________.
|         z/OS (MVS)         |
| .__________________________|
| |CICS: sous-système de MVS,|
| |moteur transactionnel ou  |
| |interactif (temps réel)   |
| |__________________________|
| |TSO: invite de commande   |
| | .________________________|
| | |ISPF (interactive system|
| | |productivity facility)  |
| | |-> typiquement TUI.     |
|____________________________|

Schéma d’architecture z/OS Exemple d’utilisation :

Avantage du mainframe: gros uptime (~90% du temps), voire 100% si CICS plexé (=plusieurs CICS en communication).

RACF: gestion utilisateurs & droits.

Pas de dossiers sur z/OS, mais une nomenclature de nommage de fichiers (exemple: noms commençant par SYS1.)

z/OS

Connexion / déconnexion avec logon / logoff.

Déplacement dans les menus avec les numéros de menu. Possibilité de faire la navigation avec un 3.4.5 par exemple.

Pour créer puis éditer un fichier:

****** ***************************** Top of Data ******************************
000100 Une piste = 56ko 
000200 Un cylindre = 15 pistes 
000300 -> 840 ko 
000400 Un nouveau fichier, 
000500 spécifier le nom (avec des quotes) 
000600 donner l'unite, le nombre primaire 
000700 et le secondaire qui pourra augmenter 15 fois la taille du fichier. 
000800 Donner aussi la taille de ligne (80). 
000810 * 
000820 * 
000830 * 
000840 * 
000900 Commandes de ligne: dans la marge, 
001000 i pour insérer, 
001100 rr / rr pour répéter un bloc 
001200 r pour répéter une ligne 
001300 c pour copier une ligne, 
001400 a pour coller après et b pour coller avant 
001410 o pour overlay, coller par dessus (ne peut écraser que des blancs), 
001500 c# pour copier plusieurs lignes 
001600 cc / cc pour copier un bloc. 
001700 m pour déplacer une ligne. 
001800 d pour effacer une ligne. 
001810 dd / dd pour effacer un bloc. 
001900 uc pour passer le texte en majuscule. 
002000 lc pour passer la ligne en minuscule. 
002100 ucc / ucc et lcc / lcc pour des blocs. 
002200 ts et positionner le curseur pour séparer la ligne en 2. 
002300 cols pour faire apparaître une réglette 
002400 * 
002500 * 
002600 * 
002700 * 
002800 Lignes de commande: sur la ligne command 
002900 find (f), cherche une chaîne (quoter si il y a un espace) 
002910   -> f5 pour naviguer 
003000 cols pour afficher la réglette en sticky 
003100 change (c) pour chercher / remplacer, f5, f6 pour naviguer. 
003200 change all (c all) pour remplacer partout. 
003300 res pour reset l'affichage. 
003310 prof(ile) pour un résumé des caractéristiques du fichier. 
003320 recovery on / off pour activer l'annulation 
003330 undo pour annuler si recovery set. 
003340 num on / off pour les numéros de ligne à droite 
003350   /!\ ces numéros peuvent interférer avec des données! 
003400 * 
003500 * 
003600 * 
003700 *

Travailler avec des splits

Quand on ouvre un split avec , le split est ouvert à la ligne du curseur (on prend l’habitude de placer le curseur en haut pour ça). Puis on passe d’un split à l’autre avec . On peut fermer un split avec la commande =x.

3.2: allouer l’espace mémoire pour un fichier. 3.4: chercher des fichiers par nom. Dans la liste des fichiers, v pour voir, e pour éditer, i pour infos, … (/ pour la liste des commandes disponibles)

On peut accéder un menu directement avec la commande ‘=numMenu’

Copier un fichier dans un autre

Dans la source, sélectionner les lignes à copier (c, cc, …) entrer cut dans la ligne de commande, entrer Dans la destination choisir le lieu de la copie (a, b, o) entrer paste dans la ligne de commande, entrer

Attention: le registre est supprimé si on fait un reset dans le buffer.

copy ‘filename’ dans la ligne de commande pour copier l’intégralité du fichier.

create ‘nom.fichier’ dans la ligne de commande avec une sélection des lignes à copier (c99999) pour dupliquer le contenu d’un fichier. Une invite propose de prendre les mêmes paramètres pour l’alloc.

Fichiers PDS

Fichiers ‘répertoires’ dans lesquels on peut trouver en quelque sorte d’autres fichiers (membres). Les PDS sont prévus pour ranger les fichiers source d’un code. On accède aux différents sous fichiers avec des parenthèses : ‘nom.fichier.pds(membre)’

La taille de ligne est commune. C’est la plus grosse différence avec un dossier sur un autre OS.

Création d’un PDS:

Allouer un bon espace mémoire (1 ou 2 cylindres typiquement). Fichier de taille fixe bloquée (fb) par défaut. Taille de ligne 80 par standard. Directory blocks ne doit pas être à 0. C’est un espace mémoire dans lequel sont stockées les adresses des membres. Dans 1 block on peut stocker 5 membres. Pour data name type on choisit pds.

En mode édition (2), on peut créer un membre avec la syntaxe ‘nom.pds(membre)’, puis faire un save. À partir du moment où il y a au moins un membre,

Un mot sur l’encodage

Les mainframes IBM n’encodent pas en ASCII mais dans un format appelé EBCDIC table EBCDIC. On peut voir l’encodage de chaque caractère avec la commande ‘hex on’.

On utilise aussi différents formats numériques (ex. 1892):

COBOL

Divisions

  1. Identification division: nom du programme
  2. Environment division: variables d’env etc
  3. Data division: définition des données du programme
  4. Procedure division: traitement des données

Commentaires avec une * en colonne 7.

Les noms de division, section, paragraphes sont en marge A (colonne 8) et les ordres executables en marge B (colonne 12)

Déclaration de variables

  1. Nombre niveau
  2. Nom
  3. Picture

exemples A(8)B(12) 77 WS-TITRE PIC X(15). -> variable nommée ws-titre de 15 alphanum (x(15)) genre ‘EXEMPLE COBOL2’ 77 WS-PAGE PIC 99. -> variable nommée ws-page de 2 décimaux (99, ou 9(2)) genre 15 77 WS-MOYENNE PIC 99v99. -> variable nommée ws-moyenne genre 16.50 (on place la virgule) 77 WS-TOTAL PIC S9(4)V99. -> variabel ws-total genre +1574.75.

Zone numérique: 18 chiffres max Alphanum: max 32768 caractères

D’autres pictures qui existent: a (alphabétique pure), des spéciaux et des compléments pour déclarer des formats alternatifs.

Zones groupe

Définir une structure élève avec nom prénom et moyenne:

        01  WS-ELEVE.
            05  WS-ID-ELEVE.
                10  WS-NOM      PIC X(11).
                10  WS-PRENOM   PIC X(11).
            05  WS-MOYENNE      PIC S99V99.

-> Considéré comme un PIC X(26)

Initialisation des variables

Initialisation en working-storage

Avec le mot clé VALUE, suivi de

En PIC X

Procédures

Affichage

L’affichage de données s’effectue avec la directive DISPLAY.

Terminer un programme

On termine un programme avec la directive STOP RUN, sinon on aura une erreur au runtime (bien que le programme se sera executé).

Complilation

Pour compiler un code Cobol, on utilise un compilateur rangé dans un PDS JCL. On renseigne les variables de la compilation et on peut lancer le job avec la commande SUB(MIT).

Entités du compilateur: * IGY est le compilateur objet. * IEWL est le linker.

Messages d’erreur: * MAXCC=0, pas d’erreur de compilation. * 4 pour des warnings, * 8 pour des erreurs * 12, erreurs sévères, * 16, erreurs terminal.

Une fois le job lancé, on peut aller vérifier dans le SDSF (menu m.5) l’état de compilation. * Un résumé du job (chercher des messages d’erreur) * Une recopie du jcl (long pour une compilation) * Des STMT N0 donnant des infos ou erreus. * Une recopie du programme COBOL. * Tout en bas (m-) un summary report avec un résumé des erreurs.

Arithmétique

On utilise les fonctions: * ADD … TO …, ou ADD …. (space separated list) GIVING … (space separated list, toutes la même valeur) * SUBTRACT … FROM * MULTIPLY … BY … * DIVIDE … BY … * COMPUTE … = ……. (écriture arithmétique standard)

On peut utiliser des GIVING pour chaque opération.

Travail sur des fichiers

Fichiers séquentiels

Pour déclarer un fichier en entrée (ou sortie), on va écrire des déclarations:

Il est aussi possible dans la file section de préciser la structure du buffer avec une zone groupe.

Contrôle

if-then-else

IF … THEN … (ELSE …) END-IF.

evaluate-when

EVALUATE … WHEN val PERFORM … WHEN val’ PERFORM … … END-EVALUATE

perform-until

pour répéter des opérations avec un contrôle, on utilise

PERFORM (TEST AFTER / BEFORE) UNTIL condition … END-PERFORM.

Écrire dans des fichiers

Comme pour un fichier en lecture, pour la déclaration du fichier.

Au moment de l’ouverture du fichier, on déclare l’ouverture en mode OUTPUT.

Pour écrire, on utilise la directive write NOMINTERNE (from …)

SQL

Apperçu

SQL pour Structured Query Language.

C’est un langage qui permet d’interagir avec des bases de données organisées en tables. Une table ressemble en tout point à une portion de tableur: on dit que chaque donnée d’une table est une ligne dont les propriétés constituent des colonnes.

L’idée centrale est d’utiliser des liens entre des tables pour créer des données riches sans devoir remplir des dizaines de colonnes pour chaque table.

On interagit avec un serveut MySQL à l’aide du langage SQL et de directives. On peut placer plusieurs directives à la suite dans un fichier sql : c’est un script sql qui sera interprété ligne par ligne. On a les conventions suivantes: * La casse est sans importance, on peut écrire aussi bien select que SELECT que SeLeCt. * Chaque directive se termine par un ;. * Les sauts de ligne et l’indentation sont sans importance et uniquement ajoutés pour la clarté. * Les commentaires se font à l’aide de # ..., -- ... ou /* .... */ (ce dernier est multiligne).

Il devrait être possible de copier chaque bloc de code de ce document pour créer et suivre l’exemple développé.

Création d’une base et de tables

Bases de données

Directives pour créer et supprimer une base de donnée:

-- crée la base nommée db_exemple, renvoie une erreur si la base existe déjà
create database db_exemple;
-- crée la base de donnée db_exemple, sauf si elle existe déjà
create database if not exists db_exemple;
-- supprime la base db_exemple
drop database db_exemple;

Tables et champs

Pour être créée, une table doit avoir au moins une colonne. Chaque colonne est caractérisée par son nom et son type. Les types les plus fréquents sont: * integer pour représenter des nombres entiers * varchar(TAILLE) pour une chaîne de caractères de longueur max TAILLE. * date pour une date au format YYYY-MM-DD. * decimal(n,p) pour un nombre décimal avec un total de n chiffres, dont p après la virgule.

Directives pour créer une nouvelle table dans une (nouvelle) base:

-- On crée une nouvelle base de données.
create database if not exists db_exemple;
-- On se place dans cette nouvelle base pour créer les tables.
use db_exemple;
-- On crée une nouvelle table
-- Cette table contient une unique colonne appelée colonne_test de type entier.
create table table_test(
    colonne_test integer
);
-- On supprime la table
drop table table_test;

Bien sûr, on peut déclarer plusieurs colonnes d’un coup en les séparant par des virgules:

-- Crée une table hotels avec trois colonnes.
create table hotels(
    idHotel integer,
    nomHotel varchar(80),
    etoiles varchar(10)
);

On peut aussi ajouter ou supprimer des colonnes après la création:

-- Ajoute la colonne nombreChambres
alter table hotels add nombreChambres integer;
-- La table hotels a maintenant quatre colonnes,
-- Mais la dernière était juste pour l'exemple.
-- Supprimons la:
alter table hotels drop nombreChambres;

Et il est possible d’ajouter des contraintes sur les colonnes en les ajoutant après le type. Par exemple, pour avoir une colonne etoiles qui soit un entier à 1 par défaut, et compris entre 1 et 5 on peut déclarer:

alter table hotels drop etoiles;
alter table hotels add etoiles integer
    -- donner une valeur par défaut. Le mot default est optionnel
    default 1
    -- vérifier que la valeur est entre 1 et 5
    check (etoiles between 1 and 5);

Clés

On souhaite pouvoir lier les données entre les tables. Il nous faut donc un mécanisme pour ajouter des références entre des tables. Pour cela, on va donner à chaque table une colonne appelée clé primaire, et on va lier des tables entre elles en déclarant des colonnes qu’on appellera les clés étrangères.

Pour déclarer une colonne d’une table comme clé primaire on peut utiliser la directive suivante:

alter table hotels add primary key (idHotel);
-- Et si on voulait supprimer la propriété de clé primaire:
-- alter table drop primary key;

Cependant, dans la pratique on déclare la clé primaire au moment de la déclaration avec les attibuts suivants:

On voit donc plutôt des déclarations comme:

create table typesChambres (
    idTypeChambre integer auto_increment not null primary key,
    nombreLit integer,
    typeLit varchar(20) 'lit simple',
    descriptionChambre varchar(400)
);

On peut aussi déclarer la clé primaire à l’aide d’une contrainte, ce qui permet de nommer la déclaration (c’est une convention):

create table chambres (
    idChambre int not null auto_increment,
    numeroChambre varchar(10),
    prixParNuit decimal(7,2),
    hotelId integer,
    typeChambreId integer,
    constraint PK_chambre add primary key (idChambre)
);

Dans l’exemple développé, on a une table d’hôtels, une table qui résume les types de chambres, et une table avec des chambres. On aimerait que chaque chambre ait un type et un hôtel associés. Cette association va se faire avec des clés étrangères.

La syntaxe de déclaration pour une clé étrangère est la suivante: foreign key (NOM_CLE) references NOM_TABLE(CLE_PRIMAIRE). Pour modéliser l’exemple des hôtels on écrit donc:

-- En simple déclaration
alter table chambres add foreign key (hotelId) references hotels(idHotel);
-- ou à l'aide d'une contrainte
alter table chambres add constraint FK_chambre_typeChambre foreign key (typeChambreId) references typesChambres(idTypeChambre);

Et bien sûr on peut déclarer les clés étrangères au moment de la création de la table:

create table reservations (
    idReservation integer not null auto_increment,
    chambreId integer,
    dateDebut date,
    dateFin date check (dateFin > dateDebut),
    primary key (idReservation),
    foreign key (chambreId) references chambres(idChambre)
);

Traitement de données avec une table

Avec une table, on a quatre opérations de base, qu’on résume par l’acronyme CRUD:

Insérer des données dans une table

On utilise pour cela la clause insert into NOM_TABLE (COLONNE1, COLONNE2, ...) values (VALEUR1, VALEUR2, ...);.

Par exemple, on va créer un hôtel 4 étoiles appelé le Grand Budapest Hotel, et un hôtel 2 étoiles appelé le Hilbert Hotel:

-- Il est impératif de déclarer la clé primaire!
insert into hotels (idHotel, nomHotel, etoiles) values (1, 'Grand Budapest Hotel', 4);
-- Si la clé primaire est identique, MySQL va envoyer une erreur!
insert into hotels (idHotel, nomHotel, etoiles) values (2, 'Hilbert Hotel', 2);

On peut aussi insérer plusieurs lignes dans la même déclaration, si elles ont le même format. Ici, comme la clé primaire de typesChambres a l’attibut auto_increment on n’a pas besoin de la déclarer.

insert into typesChambres (nombreLit, typeLit, descriptionChambre) values
    (1, 'lit simple', 'Une chambre avec lit simple et salle de bain'),
    (1, 'lit simple', 'Un lit simple dans un dortoir'),
    (10, 'lit simple', 'Dortoir de 10 lits simples'),
    (1, 'lit double', 'Une chambre avec lit double et salle de bain'),
    (2, 'lit simple', 'Une chambre avec deux lits simples');

À moins qu’on ait déclaré des valeurs requises, on peut déclarer des lignes partielles, où toutes les colonnes ne sont pas remplies. On peut aussi déclarer les colonnes dans un ordre quelconque. Par exemple, la requête suivante va créer quatre chambre dans le Grand Budapest (2 avec lits doubles et deux avec lits simples) numérotés de 101 à 104 (c’est arbitraire), et 4 lits de dortoir dans le Hilbert numérotés 1 à 4 et deux dortoirs de 10 lits d1 et d2.

insert into chambres (idHotel, idTypeChambre, numeroChambre) values
    (1, 4, '101'), (1, 4, '102'),
    (1, 1, '103'), (1, 1, '104'),
    (2, 2, '1'), (2, 2, '2'), (2, 2, '3'), (2, 2, '4'),
    (2, 3, 'd1'), (2, 3, 'd2');
    

Accéder aux données d’une table

Pour lire les données d’une table, on utilisera des requêtes select COLONNES from NOM_TABLE;. Par exemple, pour voir le nom de tous les hotels enregistés on utilisera:

select nomHotel from hotels;
-- retourne 'Grand Budapest Hotel' et 'Hilbert Hotel'

Si on souhaite accéder à toutes les colonnes d’une table, on utilise le pattern *:

select * from chambres;

On remarque au passage que les prix non initialisés ont une valeur par défaut qui est null.

Pour raffiner un peu l’affichage on peut renommer les colonnes et faire des calculs sur les colonnes. Pour renommer une colonne on utilise le mot clé as. Les fonctions sont nombreuses et leur utilisation se fait simplement dans la déclaration des colonnes. Par exemple, on veut afficher le nom des hôtels en majuscule avec la légende “nom en majuscule”, et afficher autant de fois le symbole “*” qu’il y a d’étoiles avec la légende “qualité”. C’est possible!

select ucase(nomHotel) as 'nom en majuscule',
    repeat('*', etoiles) as 'qualité' 
    from hotels;

On peut aussi trier les résultats en utilisant une ou plusieurs colonnes pour le tri en déclarant un order by . Par exemple, pour afficher la requête précédente en commençant par les hôtels les plus étoilés, et par ordre alphabétique pour les hôtels avec le même nombre d’étoiles on déclare:

select ucase(nomHotel) as 'nom en majuscule',
    repeat('*', etoiles) as 'qualité' 
    from hotels 
    order by etoiles desc, nomHotel asc;

Sélectionner les lignes à afficher

On sait afficher des colonnes de manière assez fine, mais c’est toujours pour toutes les lignes d’une table. Pour sélectionner des lignes en particulier, on va utiliser deux directives: limit et where.

La clause limit indique simplement le nombre de lignes à afficher. On peut l’accompagner d’un offset pour sauter un certain nombre de lignes. Par exemple, je veux louer une chambre avec le plus de lits possibles. Je vais afficher la première ligne qui vient quand je trie la table typesChambres par nombre de lit décroissant:

select * from typesChambres 
    order by nombreLit desc limit 1;

Et je vois que le type de chambre n°3 est le type avec le plus de lit.

Pour restreindre l’affichage à des lignes qui remplissent certaines conditions, on va utiliser la clause where. Par exemple, pour connaître les hôtels qui ont des chambres de type n°3 avec beaucoup de lits, je pourrais maintenant envoyer la requête:

select hotelId, numeroChambre 
    from chambres where typeChambreId = 3;

Agglomérer des lignes

Il peut arriver qu’on cherche une information spécifique sur un sous ensemble de lignes d’une table. Par exemple, on peut vouloir connaître le plus grand numéro de chambre qui existe par type de chambre, ou bien le nombre de chambre qui ont un prix (une valeur non null) par hôtel. Ces requêtes utilisent un group by pour définir les sous ensembles, et des fonctions pour les colonnes. Pour les deux exemples donnés, les requêtes sont:

select typeChambreId, max(numeroChambre) from chambres group by typeChambreId;
select hotelId, count(prixParNuit) from chambres group by hotelId;

Les résultats de la dernière requête devraient indiquer qu’il faut aller modifier les prix des chambres…

Modifier les données d’une table

Pour modifier des données, on va utiliser une requête de la forme update NOM_TABLE set COLONNE = ...;. À droite de l’égalité, on peut utiliser n’importe quelle fonction qui utilisera les valeurs de la ligne pour actualiser la nouvelle valeur.

Pour commencer simplement, on va mettre à 30 tous les prix de la table chambres:

update chambres set prixParNuit = 30;

Pour modifier seulement une partie des données on peut utiliser la clause where. Mettons les lits en dortoir de l’hôtel Hilbert à 10.75 par nuit:

update chambres set prixParNuit = 10.75 
    where typeChambreId = 2 and hotelId = 2;

Et on peut utiliser des fonctions qui utilisent les valeurs des lignes comme nouvelle valeur. Par exemple le Grand Budapest Hotel décide que leurs chambres couteront 50 + typeChambreId² - hotelId…

update chambres 
    set prixParNuit = 50 + typeChambreId * typeChambreId - hotelId 
    where hotelId = 1;

On peut aussi utiliser un select pour filtrer les lignes qui vont être modifiées. Par exemple, le gouvernement du monde a décrété qu’une réduction de 10% devait être faite dans tous les hôtels sur toute chambre de 5 lits ou plus.

-- Sélectionne l'id de tous les types de chambre qui ont 5 lits ou plus:
select idTypeChambre from typesChambres where nombreLit >= 5;
-- On peut utiliser ce select pour faire un update:
update chambres set prixParNuit = 0.9 * prixParNuit 
    where typeChambreId in (
    select idTypeChambre from typesChambres where nombreLit >= 5
    );

Supprimer des données

La suppression des données se fait avec la directive delete from NOM_TABLE;. Pour ne supprimer qu’une partie des données, on procédera de même que pour l’update.

Jointures

Les trois jointures principales Les jointures sont une famille de clauses qui permettent de combiner plusieurs tables entre elles, pour mettre en évidence les liens créés par les clés.

Bien que ce ne soit pas la seule manière de combiner des tables (on peut faire beaucoup avec where et plusieurs tables dans un select), les jointures sont la manière plus idiomatique de procéder. On va présenter la manière standard d’utiliser les jointures, c’est à dire avec la syntaxe suivante: select ... from TABLE_A a [left | right] join TABLE_B b on a.PK_A = b.FK_B ...;. On laisse de côté les jointures sans on et les jointures avec autre chose que les clés dans le test du on. On considère de plus que la table TABLE_B est celle qui contient une clé étrangère qui pointe sur TABLE_A.

Inner joins

La jointure interne est la jointure par défaut, qui s’appelle avec join. On obtient une table qui contient les lignes de TABLE_B qui font effectivement référence à TABLE_A (c’est à dire dont la clé étrangère n’est pas null). Le nombre de ligne de la jointure interne est donc inférieur ou égal au nombre de lignes de TABLE_B. Les lignes de la jointure combinent les informations des deux tables. Les champs PK_A et FK_B n’ont plus vraiment de sens dans cette table: ils sont de toute façon égaux dans chaque ligne, et la valeur de PK_A peut se répéter entre plusieurs lignes. Par contre, chaque ligne a une valeur différente pour la clé primaire de TABLE_B.

C’est un peu comme si on avait ‘collé’ les deux tables le long des colonnes PK_A et FK_B. La jointure interne est symétrique et on aurait aussi bien pu déclarer les tables dans l’autre sens.

Pour un exemple avec les hôtels, pour écrire une requête qui affiche toutes les chambres avec le prix par nuit et le nombre d’étoiles, on va joindre les tables hotels et chambres:

select c.idChambre, c.prixParNuit 'prix par nuit', h.etoiles 'nombre d''étoiles' 
    from hotels h join chambres c 
    on h.idHotel = c.hotelId;

On peut aussi utiliser plusieurs jointures. Par exemple, pour obtenir un joli tableau récapitulatif avec pour chaque chambre: son hôtel, son prix et le type de lit, on écrit:

select c.idChambre, h.nomHotel, c.prixParNuit, t.typeLit 
    from hotels h join chambres c 
    on h.idHotel = c.hotelId join typesChambres t 
    on t.idTypeChambre = c.typeChambreId;

Left joins et right joins

Le danger de la jointure interne, c’est qu’on peut perdre des lignes: on perd toutes les lignes orphelines de TABLE_A (celles qui ne sont pas pointées dans TABLE_B), et on perd toutes les lignes sans cible de TABLE_B (qui ont une valeur null pour la clé étrangère).

Dans certains cas, on peut vouloir avoir la trace de ces lignes qui ne sont pas affichées. C’est le rôle des jointures gauche et droite. La jointure gauche left join garantit qu’on garde toutes les lignes de la table de gauche (celle qui est avant le mot join). La jointure droite right join fait l’inverse: elle garantit qu’on garde toutes les lignes de la table de droite (celle après le mot join).

Attention à la taille que peuvent avoir les tables. Mettons que la table de gauche est celle qui a la clé primaire. Alors dans un left join, on peut garantir qu’on aura au moins autant de lignes, mais on peut des répétitions des données de la table de gauche. On ne peut aussi pas garantir l’unicité d’une clé primaire, pour aucune des deux clés disponibles. Au contraire, dans un right join on peut garantir que le nombre de ligne est exactement celui de la table de droite, et que la clé primaire de la table de droite est sans répétition.

Modélisation des relations

On a des relations entre des objets avec des cardinalités différentes. Pour obtenir des modèles correspondants en SQL il existe quelques recettes.

Relation 1-to-1

Si la relation est unidirectionnelle, il y aura simplement une table avec une clé étrangère qui pointe vers l’autre table. Si la relation est bidirectionnelle par contre, il y aura une clé étrangère dans chaque table, qui devraient pointer les lignes de manière cohérente.

Relation 1-to-many

La foreign key est toujours du côté du many. Pour obtenir la liste du côté 1, on va faire une query côté many avec une clause where FOREIGN_KEY = SPECIFIC_PRIMARY_KEY.

Relation many-to-many

On ne peut pas stocker une liste de clés étrangères dans une table. On va donc passer par une table tierce appelée la table de jointure.

Supposons qu’on a deux tables a et b avec simplement un id:

create table a (id_a int auto_increment primary key, msg varchar(10));
create table b (id_b int auto_increment primary key, msg varchar(10));

On voudrait avoir une relation many-to-many entre les tables. Pour cela on va créer la table de jointure liens_a_b qui contient des foreign keys vers les deux tables a et b.

create table liens_a_b (
    id_lien int auto_increment primary key,
    a_id int,
    b_id int,
    foreign key (a_id) references a(id_a),
    foreign key (b_id) references b(id_b)
);

Pour donner un peu de volume à l’exemple on va ajouter des données: 5 entrées chez a et 4 entrées chez b.

insert into a (msg) values ('chat'), ('mésange'), ('sardine'), ('ficus'), ('quartz');
insert into b (msg) values ('respire'), ('marche'), ('vole'), ('explose');

Et pour enregistrer des liens, on peuple la table liens_a_b.

insert into liens_a_b (a_id, b_id) values
    (1, 1), (1, 2),
    (2, 1), (2, 2), (2, 3),
    (3, 1),
    (4, 1);

Pour obtenir toutes les entrées de b liées à l’entrée de a avec id 1, on peut écrire la query

select b.msg as 'que fait le chat?' 
    from b join liens_a_b as l 
    on b.id_b = l.b_id 
    where l.a_id = 1;

Et pour afficher tous les liens existants, on a la query:

select concat(a.msg, ' ', b.msg) as 'choses vraies' 
    from a join liens_a_b l 
    on a.id_a = l.a_id join b 
    on b.id_b = l.b_id;

Architecture en couches

La logique est de découper un programme en couches, chacune ayant son rôle, et de les rendre les plus indépendantes possible. Ceci aidera à garder un code maintenable plus facilement. Illustration des couches

On a trois couches principales: la couche d’accès aux données (DAL), la couche du traitement spécifique des données (BLL), et la couche d’interaction avec les utilisateurs (IHM). Ces trois couches interagissent de proche en proche: la couche DAL et la couche IHM ne sont pas directement en contact! Si un utilisateur veut accéder à une donnée il devra utiliser une des fonctions de la couche IHM qui va entrainer des actions dans la couche BLL, qui peut elle demander des données à la couche DAL.

Ce découpage ajoute de la complexité mais permet de mieux organiser le code, et idéalement d’améliorer un point précis du code sans impacter les autres couches.

Couche IHM

C’est ce qu’on appelle le frontend du code: la partie en contact avec l’utilisateur. C’est ici qu’on gère l’affichage des données et des messages de la machine, la récupération des données entrées. On peut avoir des interfaces textuelles en console, avec des sysout et des Scanner, mais on peut aussi définir des interfaces graphiques avec fenêtres, boutons etc.

Interfaces graphiques avec Swing

Swing est un kit d’éléments graphiques pour créer des interfaces graphiques. Pour créer des fenêtres, on crée une classe qui hérite de la classe JFrame.

Dans un JFrame on va placer des JPanel qui sont des conteneurs avec un layout (doc), et qui vont pouvoir contenir toutes sortes de composants: labels, champs textes, boutons…

Couche Business

Business Objects (BO)

Ne doivent pas dépendre des autres couches. Modélisation des règles métiers, de l’hérédité et des interfaces, des cardinalités.

Dans cette zone de la couche on trouvera des POJOs (Plain Old Java Objects) qui définissent les entités en jeu dans le programme. C’est le package qu’on a appelé model dans les projets.

Business Logic Layer (BLL)

C’est la zone de la couche Business où on va effectuer les calculs et la logique qui articulent les objets: calculer une TVA par exemple.

On interface la couche avec la couche d’accès aux données pour récupérer les données et c’est ici qu’on a des fonctions qui travaillent sur ces données. Un nom de package qui pourrait correspondre est service.

Data Access Layer (DAL)

C’est la couche en interaction avec le système de gestion des bases de données (SGBD). On utilise jdbc pour l’interface programme / bdd. Le package correspondant s’appelle typiquement dao pour Data Access Objects.

Toolbox

Déclaration des drivers (optionnel)

// Dépendance forte:
DriverManager.registerDriver(new org.mariadb.jdbc.Driver());

// Ou bien, en dépendance faible
Class.forName("org.mariadb.jdbc.Driver");

Connection

Pour ouvrir la connexion avec la bdd

Connection co = DriverManager.getConnection(url, user, password);

Et on ferme la connexion avec co.close().

Gérer les transactions. Pour un meilleur contrôle sur les transactions avec la bdd, on peut

co.setAutoCommit(false);
// Pour envoyer une transaction
co.commit();
// Pour vider la transaction qui se préparait (cas d'erreur)
co.rollback();

Statements

Servent à envoyer des requêtes à la BDD.

// Pour un statement sans paramètres (rare)
Statement query = co.createStatement();

// Pour créer des statements avec un template et un contrôle du typage des données
PreparedStatement queryPrep = co.prepareStatement(template);
// template est un String avec des caractères '?' là où on veut des données interchangeables.

Puis on va déclarer la query sql:

// Pour un statement préparé. Indice commence à 1
queryPrep.setInt(indice, valeur); // Ou setString, setDouble, ...

Et enfin on execute le statement:

//Pour une query qui modifie la BDD
query.executeUpdate():
// Pour une query qui ne fait que de la lecture
query.executeQuery();

ResultSets

Les résultats d’une query sont rangés dans un objet ResultSet qui a une tête de lecture sur une ligne de la table retournée et des méthodes pour obtenir les données d’une ligne.

Pour déplacer la tête de lecture:

ResultSet res;
// Retourne true s'il y a une ligne suivante à lire et place la tête de lecture dessus
res.next();

Puis pour accéder aux données dans une ligne:

// Indice commence à 1
res.getInt(indice);
res.getString(indice);

Utiliser la DAL depuis la BLL

On mettra en place des interfaces pour créer un couplage plus souple entre BLL et DAL. Par exemple, pour une classe Stagiaire, il y a deux façons de gérer les données StagiaireDAOJdbc et StagiaireDAOAutre. On va créer une interface StagiaireDAO que vont implémenter les deux classes. De cette façon, depuis la couche BLL on n’appelle que les interfaces.

Diminution du couplage entre les couches

On parle de couplage faible ou fort entre les couches pour spécifier la manière dont les couches dépendent entre elles. Un couplage fort est un couplage directement au niveau du code: si on fait un changement, il faudra venir modifier plusieurs lignes de code.

Le but est de créer une application fermée à la modification, ouverte à l’extension (Ici pour la même considération en Python).

Un empilement typique

On a dans les objets métiers une classe Exemple avec ses attributs. On va créer des implémentations de service d’accès données dans le DAL, par exemple ExempleJdbcImpl, ExempleMyBatisImpl, ExempleMongoDBImpl qui font chacune la même chose mais avec des structures différentes: bdd structurelle, fichier xml, …

Dans la couche BLL, on va avoir une classe de service qui remonte les services du DAL, ExempleServiceImpl qui fait appel à l’une des classes du DAL et qui définit des fonctions relais.

Et dans la couche IHM, on a une classe ExempleIhm qui vient appeler les fonctions de ExempleServiceImpl.

Si maintenant on souhaite changer dans ExempleServiceImpl la classe du DAL, mettons qu’on souhaite passer de ExempleJdbcImpl à ExempleMyBatisImpl. Il va falloir changer l’objet du DAL appelé, et donc aussi le paquet importé, voire si les fonctions sont nommées différemment de changer tous les autres appels de fonction.

C’est ce qu’on appelle un couplage fort.

Interface et Factory