Aller au contenu

Modélisation avec UML et tables en base de données

Diagramme de classe selon le langage de notation UML

Cours

MCD (Modèle Conceptuel de Données) de la Méthode Merise

Cours

MLD (Modèle Logique de Données) graphique (Merise)

Cours

MLD au format textuel (Merise)

A ce stade, voire même dans votre MLD, vous pouvez modifier les noms des attributs et des clefs étrangères pour les rendre plus explicites.

Personne = (#id TINYINT, prenom VARCHAR(50) , nom VARCHAR(50) );
Etudiant = (#id, diplome VARCHAR(45) );
Professeur = (#id, nom VARCHAR(50) );
Matiere = (#id TINYINT, libelle VARCHAR(50) , nombreHeures TINYINT);
Cours = (#id TINYINT, heureDebut TIME, heureFin TIME, #id_professeur, #id_matiere);
participer = ((#id_etudiant, #id_cours), numeroDePlace INT);

Script SQL Correspondant au MLD de Merise

CREATE TABLE Personne(
   id TINYINT,
   prenom VARCHAR(50)  NOT NULL,
   nom VARCHAR(50)  NOT NULL,
   PRIMARY KEY(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE Etudiant(
   id TINYINT,
   diplome VARCHAR(45) ,
   PRIMARY KEY(id),
   FOREIGN KEY(id) REFERENCES Personne(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE Professeur(
   id TINYINT,
   nom VARCHAR(50) ,
   PRIMARY KEY(id),
   UNIQUE(nom),
   FOREIGN KEY(id) REFERENCES Personne(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE Matiere(
   id TINYINT,
   libelle VARCHAR(50) ,
   nombreHeures TINYINT,
   PRIMARY KEY(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE Cours(
   id TINYINT,
   heureDebut TIME,
   heureFin TIME,
   id_professeur TINYINT NOT NULL,
   id_matiere TINYINT NOT NULL,
   PRIMARY KEY(id),
   FOREIGN KEY(id_professeur) REFERENCES Professeur(id),
   FOREIGN KEY(id_matiere) REFERENCES Matiere(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE participer(
   id_etudiant TINYINT,
   id_cours TINYINT,
   numeroDePlace INT,
   PRIMARY KEY(id_etudiant, id_cours),
   FOREIGN KEY(id_etudiant) REFERENCES Etudiant(id),
   FOREIGN KEY(id_cours) REFERENCES Cours(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

EER (enhanced entity–relationship) entité association étendu (PhpMyAdmin)

Cours

Génération des tables à partir des classes

Pour la modélisation sous forme de diagramme de classe UML, vous pouvez utiliser :

Héritage

La classe A hérite de la classe B.

2 solutions (voire 3) :

Associations

Association : 1..1

Uniquement valable dans l’univers objet avec un diagramme de classe mais invalide dans un MCD car incohérent !

2 solutions :

Association : 1..* (et agrégation)

A 1—>* B :

Association : *..*

A *—>* B :

Modèle Logique correspondant (MLD de Merise)

modèle logique

Voici les classes Java annotées de notre diagramme UML

Cours

@Entity
@Table(name="cours")
public class Cours implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(unique=true, nullable=false)
	private byte id;

	private Time heureDebut;

	private Time heureFin;

	//bi-directional many-to-one association to Professeur
	@ManyToOne
	@JoinColumn(name="id_1", nullable=false)
	private Professeur professeur;

	//bi-directional many-to-one association to Matiere
	@ManyToOne
	@JoinColumn(name="id_2", nullable=false)
	private Matiere matiere;

	//bi-directional many-to-one association to Participer
	@OneToMany(mappedBy="cours")
	private List<Participer> participation;

Etudiant

@Entity
@Table(name="etudiant")
public class Etudiant implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(unique=true, nullable=false)
	private byte id;

	@Column(length=45)
	private String diplome;

	//bi-directional one-to-one association to Personne
	@OneToOne
	@JoinColumn(name="id", nullable=false, insertable=false, updatable=false)
	private Personne personne;

	//bi-directional many-to-one association to Participer
	@OneToMany(mappedBy="etudiant")
	private List<Participer> participation;

Personne

@Entity
@Table(name="personne")
public class Personne implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(unique=true, nullable=false)
	private byte id;

	@Column(nullable=false, length=50)
	private String nom;

	@Column(nullable=false, length=50)
	private String prenom;

	//bi-directional one-to-one association to Etudiant
	@OneToOne(mappedBy="personne")
	private Etudiant etudiant;

	//bi-directional one-to-one association to Professeur
	@OneToOne(mappedBy="personne")
	private Professeur professeur;

Professeur

@Entity
@Table(name="professeur")
public class Professeur implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(unique=true, nullable=false)
	private byte id;

	@Column(length=50)
	private String nom;

	//bi-directional many-to-one association to Cours
	@OneToMany(mappedBy="professeur")
	private List<Cours> cours;

	//bi-directional one-to-one association to Personne
	@OneToOne
	@JoinColumn(name="id", nullable=false, insertable=false, updatable=false)
	private Personne personne;

Matière

@Entity
@Table(name="matiere")
public class Matiere implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(unique=true, nullable=false)
	private byte id;

	@Column(length=50)
	private String libelle;

	private byte nombreHeures;

	//bi-directional many-to-one association to Cours
	@OneToMany(mappedBy="matiere")
	private List<Cours> cours;

ParticiperPK (classe de la clef primaire composée)

@Embeddable
public class ParticiperPK implements Serializable {
	//default serial version id, required for serializable classes.
	private static final long serialVersionUID = 1L;

	@Column(insertable=false, updatable=false, unique=true, nullable=false)
	private byte idEtudiant;

	@Column(name="idCours", insertable=false, updatable=false, unique=true, nullable=false)
	private byte id1;

	public ParticiperPK() {
	}
	public byte getIdEtudiant() {
		return this.id;
	}
	public void setIdEtudiant(byte id) {
		this.id = id;
	}
	public byte getIdCours() {
		return this.id1;
	}
	public void setIdCours(byte id1) {
		this.id1 = id1;
	}

	public boolean equals(Object other) {
		if (this == other) {
			return true;
		}
		if (!(other instanceof ParticiperPK)) {
			return false;
		}
		ParticiperPK castOther = (ParticiperPK)other;
		return 
			(this.idEtudiant == castOther.idEtudiant)
			&& (this.idCours == castOther.idCours);
	}

	public int hashCode() {
		final int prime = 31;
		int hash = 17;
		hash = hash * prime + ((int) this.idEtudiant);
		hash = hash * prime + ((int) this.idCours);
		
		return hash;
	}
}

Classe Particper

@Entity
@Table(name="participer")
public class Participer implements Serializable {
	private static final long serialVersionUID = 1L;

	@EmbeddedId
	private ParticiperPK participerPK;

	private int numeroDePlace;

	//bi-directional many-to-one association to Etudiant
	@ManyToOne
	@JoinColumn(name="id_etudiant", nullable=false, insertable=false, updatable=false)
	private Etudiant etudiant;

	//bi-directional many-to-one association to Cours
	@ManyToOne
	@JoinColumn(name="id_cours", nullable=false, insertable=false, updatable=false)
	private Cours cours;

Complément sur les Clefs et les Index

Les clefs et les index permettent un accès rapide aux données et servent aussi de contrainte d’intégrité.

Clef primaire technique

La clef primaire est constituée d’un id. Choisir un identifiant unique et naturel plutôt qu’un entier auto-incrémenté qui est unique mais n’empêche pas la création de doublons sauf si vous déclarez d’autres champs comme “unique”. Le problème est que si l’identifiant natuel est de type chaîne de caractères, les performances sont amoindries par rapport à l’utilisation d’une clef de type numérique.

Les auto-incrément de clef primaire simplifie grandement les relations entre les tables mais ceci est discutable (Il a l’inconvénient de ne pas contrôler les doublons).

Personnellemment, je ne suis pas partisan des clefs auto-généréés ! Ce n’est pas une bonne pratique si vous avez la possibilité de trouver une clef naturelle ou du moins qui a un sens !

Raisons :

Il s’agit d’un clef dite id technique qui ne doit pas être visible pour l’utilisateur !

Même si la clef primaire n’est pas obligatoire en MYSQL, vous devez en déclarer une obligatoirement.

C’est une erreur de conception d’avoir une table sans clef primaire ! Malheureusement, ça existe même dans les bases de données des ESN !

Clef fonctionnelle ou clef unique

La *clef fonctionnelle (ou clef unique) est l’ensemble des champs constituants l’unicité d’un enregistrement (id primaire exclu).

L’ajout de cette clef vous oblige à déterminer l’unicité des enregistrements et contribue fortement à l’intégrité des enregistrements de la base de données.

Pour rappel : l’id est un concept technique, il ne doit pas être vu par l’utilisateur.

C’est la clef fonctionnelle qui compte pour un utilisateur.

Cette clef est appelée aussi clef unique. Elle est composée d’un ou plusieurs champs.

Champ unique

S’il y a un seul champ, on la définit grâce à une contrainte d’unicité sur le champ.

Prenons l’exemple d’un produit qui a un champ reference. Il est unique, il ne peut y avoir 2 produits qui ont la même référence !

CREATE UNIQUE INDEX `reference_UNIQUE` ON `stock`.`produit` (`reference` ASC);

Champs multiples

S’il y a plusieurs champs, on la définit grâce à un index unique composé des champs la composant.

Prenons l’exemple d’une personne qui a 4 champs : nom, prenom, dateDeNaissance, email. les nom, prenom, dateDeNaissance constitue la clef fonctionnelle.

Il ne peut pas avoir 2 personnes avec le même triplet nom, prénom et date de naissance, quoique…

CREATE UNIQUE INDEX `uk_UNIQUE` ON `carnet`.`personne` (`nom` ASC, `prenom` ASC, `dateDeNaissance` ASC);

Résumé sur les clés techniques et fonctionnelles

Une clé fonctionnelle ou unique (ou “business key” en anglais) est une clé qui a une signification sémantique ou fonctionnelle dans le contexte de l’application. Elle permet d’identifier de manière unique une entité en se basant sur des caractéristiques fonctionnelles. Par exemple, dans une application de gestion de commandes, la combinaison du numéro de commande et de la date de commande peut être considérée comme une clé fonctionnelle unique, car elle identifie de manière unique une commande.

Une clé technique est une clé qui est générée par le système et qui n’a pas de signification sémantique ou fonctionnelle pour les utilisateurs. Elle est souvent utilisée en interne par le système pour faciliter la gestion des entités. Par exemple, dans une base de données, une clé technique peut être un identifiant numérique généré automatiquement pour chaque enregistrement.

La principale différence entre les 2 types de clés est leur signification et leur utilisation :

Modification de la valeur de la clef

Dans la logique des choses, une fois l’enregistrement créé, on ne devrait pas pouvoir modifier les valeurs de la clef fonctionnelle. Ce contrôle est du domaine du développement (java, php).
Imaginez : vous créez dans la base un produit avec une référence. Vous faites une commande avec ce produit et éditez une facture. Vous changer ensuite la référence du produit. Il sera impossible à l’acheteur de retrouver son produit inscrit sur la facture.

Clef étrangère

La clef étrangère permet d’établir la relation entre les 2 tables et maintient en partie l’intégrité de la base.
C’est une contrainte de base.
Cette clef vérifie si l’enregistrement étranger existe.
Ceci est très utile lors de la création et la suppression d’un enregistrement Dans une relation 1.., c’est la table proche de * qui contient la clef étrangère.
Dans une relation *..
, on décompose en 2 relations 1..*.
Dans une relation 1..1, on décide d’une table maître.

Index

Les index permettent une lecture plus rapide de la base en fonction des champs indexés.
En théorie, on pourrait avoir autant d’index que de critères de recherche.
Cependant les index sont coûteux en place et en temps d’écriture.
De plus les processeurs et les moteurs de base de données sont devenus très performants. En fait tout dépend de l’utilisation :

Règles de normalisation des bases de données relationnelles

Il existe 6 niveaux de forme normale.
Une base de données relationnelle doit être au minimum en 3ème forme normale.
Avec les id techniques, il est très facile de ne pas respecter la normalisation.
Une base relationnelle non normalisée entraîne de graves erreurs de comportement, et oblige le développeur à écrire plus de code.

Règles de normalisation

Dans une base de données relationnelle, il y a 3 règles obligatoires à connaître.

Atomicité

Il n’y a pas de tableau, ni de liste dans un champ.
Par exemple, il est déconseillé de mettre un nom suivi du prénom dans le même champ. On doit dans ce cas créer un champ nom et un champ prénom.
De même, il est interdit de mettre un tableau (array) de noms d’article dans la table facture. On doit créer une table article contenant le nom de l’article.

Information centralisée

Les informations sur une entité sont sur un seul enregistrement de la table. Il est interdit de disperser les informations sur plusieurs enregistrements.

Information unique (Unicité)

L’information est représentée une seule fois dans la base. Il est interdit d’avoir des doublons dans la table. Pour cela il faut ajouter une contrainte d’unicité (clef unique ou fonctionnelle).
De même il est interdit de dupliquer l’information dans une autre table.
Par exemple la description d’un fournisseur ne peut pas être dans une table produit, seul y figure son id et des caractéristiques propres au produit.

Définition

Pour le fun, voici la définition mathématique.
Note : la définition a été écrite avant l’utilisation systématique des id, il faut comprendre par clef primaire la clef fonctionnelle.

1ère forme normale

Une relation est en 1ère forme normale si elle ne contient que des “valeurs atomiques”, c’est-à-dire pas de “groupes répétitifs”. Non décomposable Un champ contient une valeur au plus : pas de tableau, ni de liste.

2ème forme normale

Une relation est en 2ème forme normale si elle est déjà en 1ère forme normale, et si tout attribut n’appartenant pas à la clé (primaire) dépend complètement de cette clef.
Un champ non clé primaire ne doit pas dépendre d’une partie de la clé primaire. Il doit en dépendre entièrement.

3ème forme normale

Un champ non clé primaire ne doit pas dépendre d’un autre champ non clé primaire.
Dans ce cas, on peut décomposer la table en deux tables afin d’éviter une redondance d’informations dans la base.

Un complément d’informations

Vous voulez des explications complètes, lisez ceci.
STOP, je n’ai rien compris, lisez ceci.

Pièges à éviter lors de la conception et évolution de la base

Conclusion

Rien de compliqué, juste du bon sens et de la pratique…