2023
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)
-> 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. |
|____________________________|
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.)
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 *
Quand on ouvre un split avec
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’
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 ‘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,
Les mainframes IBM n’encodent pas en ASCII mais dans un format appelé
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):
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)
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.
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)
Avec le mot clé VALUE, suivi de
En PIC X
L’affichage de données s’effectue avec la directive DISPLAY.
On termine un programme avec la directive STOP RUN, sinon on aura une erreur au runtime (bien que le programme se sera executé).
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-
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.
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.
IF … THEN … (ELSE …) END-IF.
EVALUATE … WHEN val PERFORM … WHEN val’ PERFORM … … END-EVALUATE
pour répéter des opérations avec un contrôle, on utilise
PERFORM (TEST AFTER / BEFORE) UNTIL condition … END-PERFORM.
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 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é.
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;
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(
integer
colonne_test
);-- 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(
integer,
idHotel varchar(80),
nomHotel varchar(10)
etoiles );
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);
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 (
integer auto_increment not null primary key,
idTypeChambre integer,
nombreLit varchar(20) 'lit simple',
typeLit varchar(400)
descriptionChambre );
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 (
int not null auto_increment,
idChambre varchar(10),
numeroChambre decimal(7,2),
prixParNuit integer,
hotelId integer,
typeChambreId 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 (
integer not null auto_increment,
idReservation integer,
chambreId date,
dateDebut date check (dateFin > dateDebut),
dateFin primary key (idReservation),
foreign key (chambreId) references chambres(idChambre)
);
Avec une table, on a quatre opérations de base, qu’on résume par l’acronyme CRUD:
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');
(
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',
'*', etoiles) as 'qualité'
repeat(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',
'*', etoiles) as 'qualité'
repeat(from hotels
order by etoiles desc, nomHotel asc;
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;
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…
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
);
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
.
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
.
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;
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.
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.
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.
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
.
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 (
int auto_increment primary key,
id_lien int,
a_id int,
b_id 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;
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.
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.
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.
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…
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.
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
.
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.
// Dépendance forte:
DriverManager.registerDriver(new org.mariadb.jdbc.Driver());
// Ou bien, en dépendance faible
Class.forName("org.mariadb.jdbc.Driver");
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
.setAutoCommit(false);
co// Pour envoyer une transaction
.commit();
co// Pour vider la transaction qui se préparait (cas d'erreur)
.rollback(); co
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
.setInt(indice, valeur); // Ou setString, setDouble, ... queryPrep
Et enfin on execute le statement:
//Pour une query qui modifie la BDD
.executeUpdate():
query// Pour une query qui ne fait que de la lecture
.executeQuery(); query
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
.next(); res
Puis pour accéder aux données dans une ligne:
// Indice commence à 1
.getInt(indice);
res.getString(indice); res
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.
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).
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.