Introduction au mapping objet-relationnel
Durée2h30Objectifs 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.
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 étantpgpwd
. Vous devriez alors voir apparaître le contenu de la base de données.
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 ?
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’appelerdept_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 colonneename
, 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 formeEmp [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é.
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 :
-
on récupère une instance de l’
EntityManager
. Pour cela il faut :- 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");
- Demander à la factory d’instancier la classe
EntityManager
:
EntityManager em = emf.createEntityManager()
- 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
-
on crée un contexte transactionnel, c.-à-d. on ouvre une transaction,
-
on fait les opérations souhaitées sur l’/les entité/s,
-
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’attributdeptNumber
de l’instance seraNULL
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 variabled
, son attributdeptNumber
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
etACCOUNTING
àRennes
- cinq employés
KING
,JONES
,BLAKE
,SCOTT
etFORD
. KING
doit être le manager deJONES
et deBLAKE
etJONES
doit être le manager deSCOTT
et deFORD
KING
etJONES
travaillent dans le départementRESEARCH
etBLAKE
,SCOTT
etFORD
dansACCOUNTING
.
Pour implémenter cette méthode, vous devrez aussi :
- ajouter une méthode
affectEmp(Emp e)
à la classeDept.java
qui ajoute le nouvel employée
au département, - ajouter une méthode
addLower(Emp e)
à la classeEmp.java
qui ajoute un nouveau collaborateure
à 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 :
- Créez une methode
Emp findEmployee(EntityManager em, Integer empno)
dans la classeMain.java
qui rend l’employé dont la clé primaire vautempno
, - Créez une méthode
Set<Emp> findCollaborators(Entitymanager, Emp)
dans la classeMain.java
qui rend l’ensemble de collaborateurs de l’employéemp
- Utilisez ces méthodes pour trouver les collaborateurs de l’employé
1
lorsque l’utilisateur choisi l’option4
.
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 :
- Créez une methode
void affectCommission(EntityManager, Set<Emp> collaborators, float)
qui distribue le montantcommission
entre les employéscollaborators
. La distribution de la commission sera proportionnelle au salaire de l’employé - 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’option5
.