Introduction au mapping objet-relationnel

Durée2h30

Objectifs de l’activité

L’objectif de cette activité est d’introduire les principales notions associées à la persistance des données manipulées par une application orientée objet et de les faire pratiquer en utilisant le fournisseur JPA Eclipse Link.

Principes d’un ORM

Un ORM (« Object Relational Mapping »), comme son nom l’indique, est une solution technique pour faire le lien entre le monde objet et le monde des bases de données relationnelles. Il est donc capable :

  • de se connecter à une base de données relationnelle,
  • d’associer les objets, classes et attributs manipulés par une application avec des lignes, des tables, des colonnes et des contraintes d’une base de données relationnelle et
  • de produire automatiquement les requêtes SQL relatives aux actions CRUD (Create/Read/Update/Delete) sur les objets et de les engager en base de données.

L’intérêt principal est de masquer au développeur le fait que l’objet manipulé dans une application corresponde à un tuple (une ligne) d’une base de données, rendant ainsi indépendant la manipulation des objets et les choix du mécanisme de persistance.

Les avantages à utiliser un ORM sont les suivants :

  • abstraction quasi totale du langage SQL : c’est le rôle de l’ORM de produire le code SQL équivalent aux actions sur les objets de l’application,

  • indépendance vis-à-vis de la base de données utilisée : comme l’application ne contient pas explicitement d’information sur le SGBDR utilisé, elle n’est pas liée à une base de données précise. Vous êtes donc libres de changer votre serveur de bases de données relationnelles à tout moment.

Par contre, il y a des risques à utiliser un ORM :

  • techniquement un ORM est une nouvelle couche logicielle : ça peut donner lieu à plus de code à exécuter,

  • les concepts proposés par les ORM sont de très haut niveau et relativement subtils : mal utilisés, ces mécanismes peuvent fortement dégrader les performances de vos programmes. Il est donc impératif de bien maîtriser l’ORM utilisé afin d’éviter des gros pièges.

Java Persistence API (JPA)

Il existe des ORM pour quasiment tous les langages de programmation, il en existe donc pour Java. Lorsque vous développez une application orientée objet en Java et que vous utilisez une base de données relationnelle pour stocker de manière persistante les données (et, en particulier, les attributs des objets manipulés par l’application), vous devez faire correspondre des objets, classes et attributs avec des tables, des lignes, des colonnes et des contraintes d’integrité.

Pour ce faire, les développeurs d’une application Java ont deux choix : JDBC ou JPA.

Utiliser JDBC

JDBC (« Java DataBase Connectivity ») est une API considérée comme de bas niveau puisque les développeurs doivent écrire le code Java :

  • pour se connecter à la base de données,
  • pour répercuter dans la bonne colonne de la bonne table les modifications faites dans chaque attribut de chaque objet de votre application,
  • pour récupérer dans un attribut la valeur stockée dans la bonne colonne de la bonne table.

Vous trouverez ci-dessous un exemple simple de code JDBC qui lit des données d’une table users existante dans une base de données PostgreSQL : les lignes 1. à 5. permettent de se connecter à la base de données PostgreSQL et les lignes 6. à 12. permettent de récupérer les données de la base de données et d’initialiser les attributs d’un objet user. Ensuite le code affiche les données récupérées.

import java.sql.*;

public class PostgreSQLJDBCExample {
    public static void main(String[] args) {
        // Database connection details
        1. String url = "jdbc:postgresql://localhost:5432/your_database_name";
        2. String username = "your_username";
        3. String password = "your_password";

        // SQL query
        6. String query = "SELECT id, name, email FROM users";

        try {
            // Load the PostgreSQL JDBC driver
            4. Class.forName("org.postgresql.Driver");

            // Establish the connection
            5. Connection connection = DriverManager.getConnection(url, username, password);

            // Create a statement
            7. Statement statement = connection.createStatement();

            // Execute the query
            8. ResultSet resultSet = statement.executeQuery(query);

            // Process the result set
            while (resultSet.next()) {
			9.	User user = new User();
            10. user.setId(resultSet.getInt("id"));
            11. user.setName(resultSet.getString("name"));
            12. user.setEmail(resultSet.getString("email"));

                // Display the data
                System.out.println("User: " + user);
            }

            // Close the resources
            resultSet.close();
            statement.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Utiliser JPA

JPA (« Java Persistence API ») est une API d’ORM en Java. En utilisant cette API, tous les aspects du mapping entre les objets et la base de données sont masqués au développeur. Plus concrètement, JPA est une spécification d’ORM pour Java. JPA étant une spécification (dit autrement, un document PDF), il en existe plusieurs implémentations (on parle de fournisseurs JPA). Les trois les plus connus sont Hibernate (RedHat), Eclipse Link (de la fondation Eclipse et implémentation de référence de JPA) et Open JPA (de la fondation Apache). Ils outilisent tous JDBC en coulisses pour effectuer les opérations sur la base de données.

Relation entre JPA et JDBC

Vous trouverez ci-dessous le code équivalent au précédent mais en utilisant l’API JPA. Les lignes 1. et 2. correspondent à la connexion à la base de données et les lignes 3. et 4. à la récupération des données. Evidemment pour que cela marche, il faut donner quelques indications à l’ORM…

import jakarta.persistence.*;
import java.util.List;

public class JPAExample {
    public static void main(String[] args) {
        // Create an EntityManagerFactory
        1. EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(
            "my-persistence-unit");

        // Create an EntityManager
        2. EntityManager entityManager = entityManagerFactory.createEntityManager();

        try {
            // Create a JPQL query to fetch all users
            3. Query query = entityManager.createQuery("SELECT u FROM User u", User.class);

            // Execute the query and get the results
            4. List<User> users = query.getResultList();

            // Display the results
            for (User user : users) {
                System.out.println(user);
            }
        } finally {
            // Close the EntityManager and EntityManagerFactory
            entityManager.close();
            entityManagerFactory.close();
        }
    }
}

Dans cette activité vous découvrirez quelles informations il faut donner à l’ORM et comment le faire. Le fournisseur JPA utilisé sera Eclipse Link.

Préparation de l’activité

Pour comprendre les mécanismes associés à un ORM vous aurez besoin d’une application orientée objet qui manipule des données sous la forme d’objets et d’un SGBDR avec une base de données pour stocker les données sous forme de tables. Dans cette archive nous avons mis à votre disposition ces deux éléments. Récupérez l’archive et désarchivez la. Une fois désarchivé vous trouverez un répertoire (dossier) comrec qui contient (entre autres) :

  • les sources de l’application (src) sur laquelle vous allez travailler pendant l’activité. Elle est constituée de deux classes Java :

    • Dept.java : il s’agit d’une classe qui représente les départements d’une entreprise,
    • Main.java : contient la logique de l’application. C’est le code de cette classe qui s’exécute lorsqu’on lance l’exécution de l’application.
  • un fichier docker-compose.yml qui permet de créer un conteneur docker avec un SGBDR PostgreSQL et une application web (adminer) permettant de visualiser le contenu de la base de données.

Vous devez démarrer le conteneur puis exécuter l’application Java pour commencer à travailler sur l’activité. Placez vous dans le nouveau répertoire comrec.

Conteneur docker

Pour démarrer le conteneur, une fois dans le répertoire comrec, exécutez la commande docker compose up -d. Une fois exécuté, vous pouvez utiliser l’application Docker Desktop que vous avez installé dans une des activités de l’UE d’informatique du S5 pour vérifier que le conteneur est lancé. Si tout s’est bien passé, vous y trouverez un conteneur appelé comrec_cn avec deux services :

  • db qui correspond au SGBDR PostgreSQL,
  • adminer. Vous pouvez utiliser cette application en allant à l’URL http://localhost:8081 avec un navigateur web. Renseignez les informations de connexion comme indiqué dans la figure ci-dessous, le mot de passe étant pgpwd. Vous devriez alors voir apparaître le contenu de la base de données.

Relation entre JPA et JDBC

Application Java

Une fois la base de données opérationnelle, on va s’intéresser à l’application elle même. Toujours dans le répertoire comrec :

  • pour compiler l’application, exécutez la commande ./mvnw clean compile sur Linux et Mac ou .\mvnw clean compile sur Windows
  • pour l’exécuter : ./mvnw exec:java sur Linux et Mac ou .\mvnw exec:java sous Windows
  • pour faire les deux à la fois : ./mvnw clean compile exec:java sur Linux et Mac ou .\mvnw clean compile exec:java sur Windows

Une fois son exécution lancée, un menu interactif devrait vous être proposé qui attend un entier entre 1 et 2. Dans la version fournie, la seule option qui réalise quelque chose est l’option 1 qui exécute la méthode createDepartments(). Exécutez la et vérifiez le constenu de la base de données.

Une fois que vous etes sûrs que tout est opérationnel, vous allez commencer l’étude de JPA.

Connexion à la base de données

Comme mentionné en début du sujet, un ORM doit être capable de se connecter à la base de données qui va stocker les informations manipulées par l’application. Pour ce faire, il faut que le programmeur fournisse, dans un fichier de configuration, les informations nécessaires à cette connexion. Dans le cas de JPA, c’est le fichier persistence.xml dans src/main/ressources/META-INF (src\main\ressources\META-INF sur Windows) qui doit contenir cette information. On doit y retrouver notamment l’adresse où se trouve la base de données, et le login et mot de passe que l’application doit utiliser pour se connecter.

Le fichier de configuration

Pour votre application, en analysant le contenu du fichier persistence.xml, trouvez les informations suivantes :

  • le nom de la base de données PostgreSQL qui sera utilisée,
  • le login utilisé pour accéder à la base,
  • le mot de passe correspondant à ce login,
  • le mode d’interaction entre l’application et la base de données (propriété ddl-generation),
  • le nom des classes Java dont les instances seront « persistées » (on dit qu’elles seront mappées dans la base de données),
  • le fournisseur JPA à utiliser.

Concernant le mode d’interaction ddl-generation, quelques précisions sont nécessaires. Ce paramètre indique à JPA comment il doit gérer la base de données lorsque l’application est exécutée :

  • drop-and-create-tables : à chaque démarrage de l’application, la base de données est recréée à partir de la structure du code. Ce mode est notamment utilisé lors de la phase de test de l’application.

  • none : aucun ajustement de la base de données n’est effectué. Ce mode est utilisé en production une fois que l’on est certain que la base de données est conforme au code Java et inversement.

  • create-tables : à chaque démarrage de l’application, le schéma de la base de données est comparé à celui de l’application dont le code a pu évoluer. Dans ce cas, la base de données est mise à jour en fonction du code, ce qui est généralement utilisé lors de la phase de développement de l’application.

Toutes ces informations sont regroupées dans un élément <persistence-unit name="comrec-persistence-unit" transaction-type="RESOURCE_LOCAL"> qui les identifie avec un nom (comrec-persistence-unit).

Question 1

Assurez-vous que les services PostgreSQL et adminer sont démarrés (vous pouvez utiliser l’application Docker Desktop). Allez directement à l’URL d’accès à adminer. Authentifiez-vous (si vous ne l’avez pas encore fait) avec les informations données plus haut.

Quelles sont les tables de la base ? Quelles données elles contiennent ?

Question 2

Lancez l’exécution de l’application. Analysez à nouveau les tables de la base de données ? Y a t’il eu des changements ? Lesquels ?

Question 3

Changez la valeur de la propriété ddl-generation pour qu’elle soit none. A l’aide de Docker Desktop supprimez le service db et relancez le (docker compose up -d). Analysez les tables de la base de données, lancez l’application et regardez à nouveau la base. Y a t’il eu des changements ? Lesquels ?

Important

Modifiez à nouveau la valeur de la propriété ddl-generation pour qu’elle soit drop-and-create-tables à nouveau. Supprimez le service db et relancez le (docker compose up -d). La base de données est ainsi dans l’état de départ.

Mapping d’une classe

Comme mentionné précédemment, un ORM (et donc tout fournisseur JPA) doit fournir les moyens de faire correspondre des classes, objets et attributs (les éléments du monde objet en termes de programmation) à des tables, lignes, colonnes (et contraintes) d’une base de données relationnelles. Plus concrètement, ce mapping doit faire correspondre :

  • une classe (dans le sens programmation) avec une table de la base de données,
  • un objet (une instance d’une classe) avec une ligne,
  • un attribut d’un objet avec une colonne d’une table de la base de données.

Pour faire ces correspondances en JPA il faut utiliser des annotations.

Mapping classe - table

Dans le jargon JPA, les classes qui doivent être persistées dans une base de données relationnelles sont appelées entités. Pour déclarer une classe comme une entité, il suffit de la précéder de l’annotation @Entity. Ainsi, par exemple, lorsque le code ci-dessous est exécuté par le fournisseur JPA, une table dept sera créée dans la base de données (même nom que la classe) et les instances de cette classe seront persistées comme des tuples (des lignes) de la table.

@Entity
public class Dept {
    ...
}

Lorsqu’on souhaite donner à la table un nom différent de celui de la classe, l’annotation @Table doit être utilisée. Ici, les instances de la classe Dept seront persistées comme des lignes de la table departments.

@Entity
@Table(name = "departments")
public class Dept {
    ...
}

Mapping attribut - colonne

Par défaut, tous les attributs d’une entité sont persistés, le nom de la colonne correspondante dans la base de données étant le même que celui de l’attribut. Pour modifier ce nom, il suffit de précéder l’attribut par l’annotation @Column comme dans le code ci-dessous, où la colonne de la table departments sera dept_name et pas dname.

@Entity
@Table(name = "departments")
public class Dept {
    ...
	@Column(name="dept_name")
	private String dName;
	...
}

Question 4

D’après les annotations dans la classe Dept.java fournie, répondez aux questions suivantes :

  • quel est le nom de table qui contiendra les instances de la classe ?
  • quel est le nom de la colonne correspondant à l’attribut dName ?
  • modifiez le nom de la colonne correspondant à l’attribut deptNumber, elle doit s’appeler dept_no
Contraintes de clé primaire

Pour indiquer qu’un attribut doit correspondre à la clé primaire de la table, il faut le précéder de l’annotation @Id. Ainsi, dans le code ci-dessous, la classe Dept a un attribut deptNumber qui correspond à la colonne deptnumber qui sera la clé primaire de la table. Les valeurs de la clé dovient par contre être attribuées manuellement lors de l’insertion d’un nouveau tuple (un nouveau département). Cela convient généralement lorsqu’une entité possède un identifiant naturel. Par exemple, un ISBN est attribué à tous les livres publiés. Si nous devions créer un référentiel de livres, nous pourrions l’utiliser comme identifiant et l’attribuer manuellement à chaque livre.

@Entity
@Table(name = "departments")
public class Dept {
    ...
    @Id
	private Integer deptNumber;

	private String dname;
	...
}

Pour tout le reste (et c’est, en principe, le cas pour les départements), l’implémentation JPA peut générer automatiquement des valeurs lors de l’insertion de nouvelles entités. Pour ce faire, il faut ajouter l’annotation @GeneratedValue. JPa propose trois stratégies différentes pour générer les valeurs de la clé primaire, vous trouverez ici des informations sur ces stratégies. Dans le code ci-dessous, la stratégie IDENTITY est utilisée : elle génère des entiers de 1 à 231.

@Entity
@Table(name = "departments")
public class Dept {
    ...
    @Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "dept_no")
	private Integer deptNumber;

	@Column(name="dept_name")
	private String dname;
	...
}

D’autres éléments d’une entité

Pour que les instances d’une classe puissent être manipulées par JPA il faut en plus deux autres éléments :

  • la classe doit contenir un constructeur sans paramètres,
  • pour tous les attributs de la classe, elle doit offrir des accesseurs (des getters et des setters).

Exercice 1

Ajoutez une classe Emp.java à votre application. Elle représentera les employés d’une entreprise et sera une entité JPA mappée à une table employees. Pour l’instant la classe doit avoir les attributs suivants :

  • Integer empno qui sera la clé primaire de la table dont les valeurs seront générés automatiquement par JPA,
  • String name, mappé dans une colonne ename, et qui représente le nom de l’employé,
  • String job, représente le travail fait par l’employé (ANALYST, SALESMAN, MANAGER, CLERK…),
  • Date hiredate, correspond à la date d’embauche de l’employé,
  • float salary, correspond au salaire de l’employé,
  • float commission, il s’agit de dernière commission perçue par l’employé,
  • Emp(String name, String job, Date hiredate, float salary, float commission), un constructeur pour faciliter la création d’objets,
  • String toString(), qui doit donner une description de l’employé de la forme Emp [empno, name, job, hiredate, salary (commission)]

Vérifiez que vous avez bien ajouté la nouvelle entité dans le fichier persistence.xml avant d’exécuter l’application. Quel est le contenu de la base de données ?

Mapping des associations entre classes

Comme vous le savez déjà, une application objet est constituée d’un ensemble de classes qui sont reliées entre elles par des associations. Pour en tenir compte, JPA définit un ensemble d’annotations dont vous en étudierez ici deux pour introduire les notions principales mais vous pouvez trouver des informations sur les autres annotations ici.

Pour introduire ces annotations, nous allons considérer le schéma conceptuel de la figure ci-dessous. On trouve les classes Dept et Emp sur lesquelles vous avez travaillé jusqu’ici. L’association work at est une association bidirectionnelle et indique, pour un département, les employés qui y travaillent et pour un employé, à quel département il est rattaché.

Schéma conceptuel de données

En objet, cette association se traduit par un attribut private Dept dept dans la classe Emp et un autre private Set<Emp> emps dans la classe Dept. Pour mapper cette association, deux annotations sont nécessaires (voir le code ci-dessous), @ManyToOne et @OneToMany. La première indique que plusieurs instances d’une classe (Emp ici) peuvent être reliées à la même instance d’une autre classe (Dept). La deuxième annotation indique qu’une instance d’une classe (Dept) peut être reliée à plusieurs instances d’une autre classe (Emp). Le paramètre mappedBy="dept" indique quel attribut de la classe Emp correspond à l’association bidirectionnelle (ici l’attribut dept). Ce mapping se traduit par la création, dans la table employees, d’une clé étrangère vers la table departments. Le nom de la colonne qui correspond à la clé étrangère sera celui de l’attribut. Si l’on souhaite changer ce nom, il suffit d’utiliser l’annotation @JoinColumn. Ainsi, par exemple l’ajout de l’annotation @JoinColumn(name="dept_fk")permettra de donner le nom dept_fk à la colonne qui est clé étrangère.

@Entity
@Table(name="employees")
public class Emp {
	...
	@ManyToOne
	@JoinColumn(name="dept_fk")
	private Dept dept;
	...
}
@Entity
@Table(name = "departments")
public class Dept {
    ...
    @OneToMany(mappedBy="dept")
	private Set<Emp> emps;
	...
}

Question 5

Modifiez les classes Dept.java et Emp.java pour implémenter l’association work at telle que décrite précédemment (pensez à ajouter les accesseurs des nouveaux attributs et à modifier la méthode toString qui donne une description de l’instance). Exécutez l’application et vérifiez que les changements dans les tables de la base de données ont bien eu lieu.

Exercice 2

Modifiez la classe Emp.java pour implémenter l’association manager-lowers. Le nom de la clé étrangère doit être mngr_fk (à nouveau, pensez à modifier la méthode toString). Vérifiez que les changements dans la table employees ont bien eu lieu. Quels sont ces changements ?

L’Entity Manager

Les annotations JPA que nous avons vues, ne servent à rien si elles ne sont pas exploitées dans le programme. Dans JPA, l’interface centrale qui va exploiter ces annotations est la classe EntityManager. À partir d’une instance de cette classe, un programme peut manipuler les entités afin de les créer, les modifier, les récupérer dans le programme à partir de la base de données ou les supprimer. Pour cela, la classe EntityManager offre six méthodes (find, persist, merge, detach, refresh et remove). L’instance de l’EntityManager sur laquelle on exécute ces méthodes prendra en charge la relation des objets avec la base de données et la génération des requêtes SQL nécessaires en fonction des méthodes appelées.

Le modèle de programmation avec JPA est le suivant. Lorsqu’on souhaite travailler avec des entités :

  1. on récupère une instance de l’EntityManager. Pour cela il faut :

    1. Créer une instance d’une classe particulière qu’on appele factory en indiquant le nom de l’unité de persistence à utiliser (rappelez vous, le nom a été donné dans le fichier persistence.xml) :

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("comrec-persistence-unit");

    1. Demander à la factory d’instancier la classe EntityManager :

    EntityManager em = emf.createEntityManager()

  2. on crée un contexte transactionnel, c.-à-d. on ouvre une transaction,

  3. on fait les opérations souhaitées sur l’/les entité/s,

  4. on valide ou on annule le contexte transactionnel, c.-à-d. on valide ou annule la transaction.

Le contexte transactionnel

Une transaction est un ensemble d’opérations faites sur des données dans une base de données relationnelles avec la caractéristique qu’elles seront toutes validées (commit) ou toutes annulées (rollback). Par exemple, le code ci-dessous correspond à deux requêtes SQL (un insert et un update) faites sur la base de données avec des départements. Après le commit() un nouveau département sera présent dans la table departments et le département avec dept_no = 1 s’appelera DSD et pas LUSSI comme initialement. Par contre, si au lieu du commit() on a un rollback(), alors rien ne sera fait dans la base de données (ni l’insertion ni la mise à jour).

begin();
INSERT INTO departments (dname) VALUES ("MO");
UPDATE SET dname = 'DSD' WHERE dept_no = 1;
commit();

Ce mécanisme des transactions est pris en compte dans JPA. Le code ci-dessous montre comment on crée un contexte transactionnel (ouvre une transaction) et comment on valide et annule une transaction.

1. EntityManager em = ... // Instanciation d'un EntityManager

2. em.getTransaction().begin(); // On ouvre une transaction

try {
     // Utilisation des méthodes de l'EntityManager

     // Si pas d'erreur, on valide la transaction
3.   em.getTransaction().commit();
}
catch (RuntimeException e) {
	 // Si quelque chose se passe mal, alors on annule la transaction
4.   em.getTransaction().rollback();
     throw e;
}

Les entités manipulées dans un contexte trasactionnel via l’EntityManager (c.-à-d. entre les lignes 2. et 3. ou 4.) sont dites gérées par l’EntityManager, c.-à-d. la valeur des attributs sera synchronisée avec celle de la base de données. Une fois la transaction validée (ou annulée) les entités sont dites détachées, c.-à-d. leur lien avec la base de données est perdu. Si d’autres opérations sont à faire sur celles-ci il faudra explicitement les inclure dans le nouveau contexte transactionnel.

Question 6

Complétez le code de la méthode createEmployees(EntityManager) de la classe Main.java. Elle doit permettre de créer trois employés (king, jones et smith) dans la base de données et doit afficher à la fin les employés existant dans la base de données. Quelques informations pour vous aider à écrire la méthode :

  • Pour la date d’embauche, utilisez la classe SimpleDateFormat de Java. Par exemple, pour un employé embauché le 17/12/80, la ligne à écrire serait : Date hiredate = new SimpleDateFormat("dd-mm-yy").parse("17-12-80").
  • Dans un premier temps, aucun employé n’a de manager ni de département d’affectation (valeur NULL pour ces attributs).
  • Inspirez vous du code de la méthode createDepartments pour écrire le code permettant d’afficher les employées existant dans la base de données.

Une fois la méthode completée, modifiez la méthode main pour que, lorsqu’on sélectionne l’option 2, les méthodes createDepartments et la nouvelle méthode createEmployees soient exécutées. Vérifiez le bon fonctionnement de la méthode.

Les méthodes de l’Entity Manager

Nous présentons ici une partie des méthodes uniquement, celles dont vous aurez besoin pour le projet de l’UE. Vous trouverez plus de détails sur ces méthodes et toutes les autres ici et ici.

La méthode persist

Comme vous l’avez déjà constatée, elle permet de modifier la base de données pour tenir compte de la création d’une nouvelle entité. Un détail important sur lequel on n’a pas insisté tout à l’heure c’est l’affectation d’une valeur à l’attribut correspondant à la clé primaire de l’entité. En fonction des annotations utilisées, cette valeur doit être donnée par le SGBDR. Par exemple, dans le code ci-dessous :

  • la ligne 2. crée une instance en mémoire de la classe Dept (aucune opération dans la base de données n’est réalisée). La valeur de l’attribut deptNumber de l’instance sera NULL puisqu’elle doit être générée par la base de données,
  • la ligne 3. demande à JPA de créer un nouveau tuple dans la table departments. L’entité persistée est récupérée dans la variable d, son attribut deptNumber aura donc la valeur de la clé primaire générée par la base de données.
1. EntityManager em = ... 	// Instanciation d'un Entity Manager

try{
	em.getTransaction.begin();  // Ouverture d'une transaction

	2. Dept d = new Dept("Info", "Brest"); // Création en mémoire de l'instance. La valeur de l'attribut deptNumber est null

	3. em.persist(d); // Création d'une nouvelle ligne dans la table departments. deptNumber vaut la clé primaire

	em.getTransaction().commit(); // Validation de la transaction

} catch (RuntimeException e){
	em.getTransaction().rollback(); // Annulation de la transaction si problème
}

La méthode find

Elle permet de rechercher une entité dans la base de données en donnant sa clé primaire. Par exemple, la ligne 1. du code ci-dessous, demande à l’EntityManager de chercher dans la base de données le département dont la clé primaire vaut 1. Le résultat est une instance de la classe Dept. Puisqu’il s’agit d’une opération de lecture en base de données (un SELECT), le contexte transactionnel n’est pas obligatoire, il aurait ainsi été possible de ne pas créer de contexte transactionnel avant d’appeler la méthode find (et donc pas besoin non plus de faire un commit ou un rollback).

EntityManager em = ... 	// Instanciation d'un Entity Manager
try{
	em.getTransaction.begin();  // Ouverture d'une transaction

	1. Dept d = em.find(Dept.class, 1);

	em.getTransaction().commit(); // Validation de la transaction

	System.out.println("Department name " + d.getDName()); // Affiche le nom du département 1

	} catch (RuntimeException e){
	em.getTransaction().rollback(); // Annulation de la transaction si problème
}
...

Question 7

Ajoutez la méthode createDepartmentsWithEmployees(EntityManager) à la classe Main. La méthode doit :

  • créer deux départements RESEARCH à Brest et ACCOUNTING à Rennes
  • cinq employés KING, JONES, BLAKE, SCOTT et FORD.
  • KING doit être le manager de JONES et de BLAKE et JONES doit être le manager de SCOTT et de FORD
  • KING et JONES travaillent dans le département RESEARCH et BLAKE, SCOTT et FORD dans ACCOUNTING.

Pour implémenter cette méthode, vous devrez aussi :

  • ajouter une méthode affectEmp(Emp e) à la classe Dept.java qui ajoute le nouvel employé e au département,
  • ajouter une méthode addLower(Emp e) à la classe Emp.java qui ajoute un nouveau collaborateur e à l’employé.

Modifiez la méthode main de la classe Main.java pour qu’elle soit appelée lorsqu’on sélectionne l’option 3. Vérifiez que les données ont bien été insérées dans la base de données.

Question 8

Pour cette question, vous allez modifier l’application pour qu’elle permette de trouver les collaborateurs d’un employé donné. Pour cela :

  1. Créez une methode Emp findEmployee(EntityManager em, Integer empno) dans la classe Main.java qui rend l’employé dont la clé primaire vaut empno,
  2. Créez une méthode Set<Emp> findCollaborators(Entitymanager, Emp) dans la classe Main.java qui rend l’ensemble de collaborateurs de l’employé emp
  3. Utilisez ces méthodes pour trouver les collaborateurs de l’employé 1 lorsque l’utilisateur choisi l’option 4.

La méthode merge

Cette méthode est parfois considérée comme la méthode permettant de réaliser les UPDATE des entités en base de données. Il n’en est rien et la sémantique de la méthode merge est très différente : elle attache une entité à l’EntityManager ; l’entité fera, après le merge, à nouveau partie des entités gérées par l’EntityManager.

Le code ci-dessous montre un exemple typique d’utilisation de cette méthode. L’entité d est récupérée de la base de données (ligne 1.) et on souhaite faire des opérations sur cette entité, par exemple changer le nom du département. Puisqu’il s’agit d’une opération de modification, on crée une transaction (ligne 2.), on modifie l’objet en mémoire (ligne 3.) et on demande à valider la transaction (ligne 4.). Le résultat est néanmoins une exception de type Entity not Managed puisque l’entité d ne fait pas partie des entités gérées par l’EntityManager au moment du commit.

EntityManager em = ... 	// Instanciation d'un Entity Manager

1. Dept d = em.find(Dept.class, 1) // Recherche du département `1` dans la base de données. Pas de contexte transactionnel explicitement

try{
	2. em.getTransaction.begin();  // Ouverture d'une transaction
	3. d.setName("DSD"); // Modification du nom du département en mémoire
	4. em.getTransaction().commit(); // Validation de la transaction
} catch(RuntimeException e){
	em.getTransaction().rollback(); // Annulation de la transaction si problème
}
...

Une solution au problème est d’appeler la méthode merge pour rattacher l’instance au nouveau contexte transactionnel (ligne 3.). Ainsi, lors du commit, l’EntityManager pourra répercuter les modifications dans la base de données puisque d fait partie des entités qu’il gère.

EntityManager em = ... 	// Instanciation d'un Entity Manager

1. Dept d = em.find(Dept.class, 1) // Recherche du département `1` dans la base de données

try{
	2. em.getTransaction.begin();  // Ouverture d'une transaction
	3. em.merge(d);
	4. d.setName("DSD"); // Modification du nom du département en mémoire
	5. em.getTransaction().commit(); // Validation de la transaction
} catch(RuntimeException e){
	em.getTransaction().rollback(); // Annulation de la transaction si problème
}
...

Question 9

Modifiez l’application pour qu’elle permette d’affecter une commission (un montant) aux collaborateurs d’un employé donné. Pour cela :

  1. Créez une methode void affectCommission(EntityManager, Set<Emp> collaborators, float) qui distribue le montant commission entre les employés collaborators. La distribution de la commission sera proportionnelle au salaire de l’employé
  2. Utilisez la nouvelle méthode et celles créées dans la question précédente pour distributer une commission de 10000 euros parmi les collaborateurs de l’employé 1 lorsque l’utilisateur choisi l’option 5.