Utilisateur:SallaDiagne/Brouillon

Une page de Wikipédia, l'encyclopédie libre.

Un mapping objet-relationnel (en anglais Object Relational Mapping ou ORM) est une technique de programmation informatique qui définit une base de données orientée objet à partir d'une base de données relationnelle. Cette technique fournit une abstraction conceptuelle grâce à une correspondance entre cette base de données et les objets d'un langage orienté objet (Par exemple Java)[1].

Le mapping objet-relationnel est très populaire car il permet de minimiser le coût du développement[2][3]. Ce mapping permet aux développeurs d'accéder aux données d'une base sans se préoccuper des détails. Le mapping objet-relationnel permet de réduire significativement la quantité de code que les développeurs ont besoin d'écrire[2][4][5].

Malgré les multiples avantages de cette technologie, de potentiels problèmes de performance peuvent être introduits par celle-ci. Les développeurs ont quelquefois des difficultés à trouver la partie de code responsable d'un accès à la base de données, et ne savent pas si un tel accès est efficace ou non. Mais il existe des corrections des performance anti-patterns d'ORM qui peuvent améliorer le temps de réponse des systèmes jusqu'à 98% (et en moyenne de 35%).[2]

Description[modifier | modifier le code]

Le mapping objet-relationnel définit une base de données orientée objet à partir d'une base de données relationnelle. Cette technique fournit une abstraction conceptuelle grâce à une correspondance entre cette base de données et les objets d'un langage orienté objet.

Exemple:

Voici un exemple de code qui n'utilise pas d'ORM.

String sql = "SELECT ... FROM persons WHERE id = 10";
DbCommand cmd = new DbCommand(connection, sql);  
Result res = cmd.Execute();
String name = res[0]["FIRST_NAME"];

L'ORM rendant les choses plus claires, il suffit juste d'écrire ceci :

Person p = repository.GetPerson(10);
String name = p.FirstName;

Plusieurs frameworks mettent des parties de code en méthode statique sur les classes elles-mêmes, ce qui signifie que les développeurs pourront faire:

Person p = Person.Get(10);

Il est aussi possible d'implémenter des systèmes de requêtes complexes, comme:

Person p = Person.Get(Person.Properties.Id == 10);

Implémentation[modifier | modifier le code]

Il existe plusieurs implémentations d'ORM dans presque tous les langages. Par exemple: Hibernate pour Java, ActiveRecord pour Ruby on Rails, Doctrine pour PHP, et SQLAlchemy pour Python. En Java, la conception de l'ORM est même standardisée par l'intermédiaire de JPA.[1]

Comparaison avec l'accès traditionnel aux données[modifier | modifier le code]

Par rapport aux techniques traditionnelles d'échange de données entre un langage orienté objet et une base de données relationnelle, ORM réduit significativement la quantité de code écrit. L'inconvénient est le haut niveau d'abstraction, ce qui veut dire que le développeur ne sait pas ce qui se passe réellement dans l'implémentation avec l'utilisation d'un outil ORM[6].

Requêtes ORM Requêtes traditionnelle pour acceder aux données
Employees emps=new Employees();
int EmpID;
Create Table in Database
emps.LoadAll();
Select * from Employees
emps.LoadByPrimaryKey(EmpID)
Select * from Employees where EmployeeID=1
emps.AddNew();
emps.FirstName=Aditya;
emps.HireDate=DateTime.Now();
emps.Save();
empID = emps.EmployeeID;
(emps returns new key value)
insert into Employees (FirstName,HireDate) values (Aditya, GetDate())
emps.MarkAsDeleted();
emps.Save();
delete from Employees
emps.Where.EmployeeID.Value=empID;
emps.Query.Load();
emps.MarkAsDeleted();
emps.Save();
delete from Employees where EmployeeID=1
emps.Where.EmployeeID.Value=empID
emps.Query.Load();
emps.LastName = "Sanjeev";
emps.Save();
update from Employees set LastName=Sanjeev where EmployeeID=2;

Data Binding[modifier | modifier le code]

Le but du Data binding c'est de permettre de lier un contrôle avec une ou plusieurs sources de données. Un exemple de comparaison est indiqué dans la table ci-dessous[6]:

Requêtes ORM Requêtes traditionnelle pour acceder aux données
Employees emps = new Employees();
emps.LoadAll();
GridView.DataSource= emps.DefaultView;
GridView.DataBind();
SqlConnection conn = new SqlConnection(conectionstring);
conn.Open();
SqlCommand command = new SqlCommand("SELECT * FROM Employees);
SqlDataReader dr = command.ExecuteReader();
GridView.DataSource = dr;
GridView.DataBind();

JDBC vs HIBERNATE[modifier | modifier le code]

Hibernate est une solution open source de type ORM qui permet de faciliter le développement de la couche persistance d'une application. Hibernate permet donc de représenter une base de données en objets Java et vice versa. Hibernate est très populaire notamment à cause de ses bonnes performances et de son ouverture à de nombreuses bases de données.

Ci-dessous une comparaison entre JDBC et HIBERNATE:[7]

JDBC HIBERNATE
Avec JDBC, les développeurs doivent écrire le code responsable de mapper la représentation de données d'un modèle d'objet avec son schéma de base de données correspondant. Hibernate fait ce mapping lui-même en utilisant un ficher XML.
Avec JDBC, le mapping automatique entre les objets Java et les tables de la base de données et la conversion inverse, doit être manuellement pris en charge par le développeur. Hibernate fournit la persistance transparente, et les développeurs n'ont pas besoin d'écrire du code pour mapper les tables de base de données aux objets d'application pendant l'interaction avec RDBMS
JDBC ne supporte que le langage SQL.

Les développeurs doivent trouver un moyen efficace pour accéder à la base de données, par exemple: choisir une requête efficace parmi plusieurs pour effectuer la même tâche.

Hibernate utilise le langage de requêtage d'Hibernate (qui est indépendant du type de base de données) qui a une syntaxe familière à au SQL et inclut un support complet pour les requêtes polymorphiques. Hibernate supporte aussi le SQL natif, et choisit un moyen efficace pour effectuer une tâche de manipulation de base de données pour une application.
À chaque changement d'une table de base de données dans une application qui utilise JDBC, il est essentiel de changer la structure de l'objet ainsi que de changer le code écrit pour mapper table-objet / objet-table Hibernate fournit ce mapping lui-même. Ce mapping entre les tables et les objets de l'application est réalisé par des fichiers XML.

Quand il y a des changements dans la base de données ou dans un table, la seule nécessité de changement est le fichier XML.

Avec JDBC, les développeurs ont la responsabilité de gérer le JDBC result set et de le convertir en objet Java pour être utilisable après dans l'application. Hibernate réduit la quantité de code à écrire en maintenant lui-même le mapping objet-table. Il renvoie les résultats à l'application dans des objets Java. Cela aide le programmeur à ne pas gérer manuellement les persistances des données, réduisant ainsi le temps de développement et les coûts de maintenance.
Avec JDBC, les manipulations de mémoire cache sont faites manuellement. Hibernate utilise la persistance transparente pour mettre en place une mémoire cache pour l'application workspace. Les données sont déplacées vers ce cache à la suite de la requête. Cela peut améliorer les performances si l'application cliente lit les mêmes données plusieurs fois pour une seule écriture.

La persistance transparente automatique permet aux développeurs de se concentrer sur le business logique au lieu de passer le temps à écrire ces fonctionnalités.

JDBC ne vérifie pas si l'utilisateur possède des données à jour. Ce contrôle doit être fait par le développeur. Hibernate permet les développeurs de définir un champ représentant la version de l'application. Grâce à ce champ, Hibernate actualise la version définie de la table de base de données relationnelle à chaque mise à jour sous forme d'un objet class Java pour cette table. Par exemple: si deux utilisateurs récupèrent la même donnée et la modifient, mais un seul utilisateur enregistre ces modifications, la version est automatiquement mise à jour suite à la modification.

Quand le deuxième utilisateur essaie d'enregistrer à son tour, Hibernate ne le permet pas car cet utilisateur n'a pas la dernière version de données.

Avantages d'ORM[modifier | modifier le code]

Il y a un certain nombre d'avantages à utiliser un ORM. Ci-dessous quatre parmi eux: [6]

Productivité[modifier | modifier le code]

Les code d'accès aux données constituent habituellement une partie importante des applications et le temps nécessaire pour écrire ce code peut être une partie importante du développement global d'une application. En utilisant un ORM la quantité de code est diminuée. Les ORMs génèrent automatiquement, et rapidement, tout le code responsable de la couche d'accès aux données basé sur le modèle de données défini par le développeur.

Conception d'une application[modifier | modifier le code]

Un bon outil d'ORM réalisé par des architectes de logiciels très qualifiés, va implémenter effectivement les "design patterns" qui permettent au développeur d'utiliser bonnes pratiques de programmation pour développer son application.

Cela peut aider à soutenir une séparation des préoccupations et développement et donc permettre parallèlement, le développement simultané des différentes couches, à savoir la couche d'application, la couche de logique de métier et la couche d'accès aux données.

Réutilisation du code[modifier | modifier le code]

Les développeurs peuvent écrire une bibliothèque de classes pour générer un DLL séparé pour le code d'accès aux données généré par ORM. De cette façon, chaque application qui utilise la bibliothèque de classe n'a pas besoin d'avoir le code d'accès aux données.

Maintenabilité des applications[modifier | modifier le code]

Tout le code généré par un ORM est probablement bien testé, les développeurs n'ont donc pas besoin d'allouer du temps à l'implémentation de ces tests. Tout ce qu'ils ont à faire est de vérifier que le code écrit produit bien le résultat attendu.

Alternatives aux ORM[modifier | modifier le code]

Slazure software architecture comparison

Récemment, des alternatives aux ORM tels que Slazure ont vu le jour. Ces bibliothèques réduisent la complexité du code en enlevant la couche d'accès aux données, et permettent d'acquérir une plus grande flexibilité de code avec une approche POCO pour travailler avec les bases de données.[8]

Performance[modifier | modifier le code]

L'inconvénient d'utiliser un ORM est la performance. Le code généré par un ORM pour accéder aux données est très complexe, plus que celui écrit par un développeur. Cela est dû au fait que les ORM sont conçus pour gérer une grande variété de scénarios d'accès aux données, beaucoup plus qu'une application a besoin d'utiliser. Donc le résultat de ce code complexe est une performance moins rapide.[6]

En plus, les développeurs peuvent ignorer quels bouts de code se traduiraient par un accès à la base de données, ni si un tel accès est inefficace. Ils ne peuvent donc pas améliorer la performance de cet ORM. Par exemple: le code qui est efficace en mémoire peut causer des problèmes quand on utilise un ORM en raison de l'excès aux données à l'issue de la récupération.

Les problèmes de performance apparaissent souvent dans des programmes à grandes échelles et peuvent mener à des timeouts pendant les transactions ou même des blocages.

Etudes sur les performances[modifier | modifier le code]

Des études ont été effectuées pour détecter et prioriser les cas de performances ORM anti-patterns. Le framework utilisé pendant ces études a été appliqué sur trois logiciels: deux d'entre eux sont open-source et le dernier est un grand système d'entreprise.

Ces études montrent que le framework peut détecter des centaines ou des milliers de cas de performance anti-patterns, même si elles priorisent la correction de ces anti-patterns en utilisant une approche statistiquement rigoureuse. La correction de ces instances anti-patterns peut améliorer le temps de réponse jusqu'à 98% (et en moyenne 35%)[2].

Ci-dessous une illustration d'un programme Java simple

@Entity
@Table (name = "company")
puplic class Company{
   @Id
   @Column(name="company_id")
   private long companyId;

   @Column(name="company_name")
   private String companyName;

   @OneToMany(mappedBy="company", fetch = FetchType.EAGER)
   private List<Department> department;

   void setCompany(String name){
      this.companyName = name;
   }
   ... d'autres setters et getters
}

Ce programme gère une relation entre deux classes (Company et Department). Dans cet exemple, il y a une relation one-to-many entre Company et Department, c'est-à-dire, un Company peut avoir plusieurs Departments. Cette relation est présenter par la notation @OneToMany au niveau de la variable instance department de la classe Company.java et @ManyToOne au niveau de la variable instance company de la classe Depatment.java.

La notation @Column montre quelle colonne dans la base de données est mappée avec la variable instance (Par exemple: la variable companyId est mappé à la colonne company id). Et la notation @Entity montre que la classe est un entité qui correspond à la table de a base de données définie par l'annotation @Table (Par exemple: la classe Company est mappé à la table company dans la base de données).

Deux performances anti-patterns qui sont fréquemment observées dans le code du monde réel[9][2] et qui peuvent éventuellement causer de graves problèmes de performance: (1) Données excessive, qui récupère des données inutilisées ou inutiles de la base de données; (2) One-by-One Processing, qui effectue de manière répététive les mêmes opérations sur la base de données.[2]

L'extrait des données en utilisant un ORM peut être LAZY ou EAGER. Cela détermine comment les données d'un objet java sont récupérés de la base de données. En utilisant la classe Company.java présentée ci-dessus comme un exemple, l'extrait des données est EAGER, cela veut dire que les objet départements vont être toujours récupérés lors qu'un objet company est initialisé (Par exemple: new Company()) même si les objets départements pourraient ne jamais être nécessaire. De l'autre côté, l'extrait des données type LAZY veut dire que les objets départements sont récupérés seulement quand leur information est nécessaire (Par exemple: department.getDepartmentName()).

Excessive Data[modifier | modifier le code]

La relation entre deux entités de classes de la base données est EAGER si ces deux classes sont toujours associées (Exemple: en accédant la classe Company, dans l'exemple ci-dessus, mènera toujours à un accès à la classe Department).

Dans ce cas, la relation EAGER peut améliorer la performance en récupérant deux entités de la base de données avec seulement une requête SQL en utilisant un jointure.

Pourtant, ce type de récupération des données n'est pas optimal si les données ne sont pas utilisés. Dans cet exemple:

Excessive.java
for(Company c : companyList){
   c.getCompanyName();
}

Si le fichier Excessive.java est exécuté en utilisant un ORM configuré dans Company.java (ci-dessus), les requêtes SQL générées vont récupérer les objets Companies et les objets Departments en raison du mode de fonctionnement EAGER de la variable d'instance department dans Company.java. Sauf que les objets départements ne sont jamais utilisés dans Excessive.java.

Les requêtes SQL générées par Excessive.java AVANT la correction
select company left outer join department where companyID=1;
select company left outer join department where companyID=2;
...

Une manière de corriger cet anti-pattern est de changer la mode de récupération des données de EAGER à LAZY.

Les requêtes SQL générées par Excessive.java APRES la correction
select company where companyID=1;
select company where companyID=2;
...

Pour montrer l'impact sur les performances, la programme motionné ci-dessus a été exécuté. Dans la base de données, 300 enregistrements de la table Company et 10 enregistrements Department pour chaque enregistrement de la table Company ont été insérés. Le temps de réponse avant la correction est 1,68 secondes; corriger cet anti-pattern permet de réduire le temps d'exécution de 0,48 seconde (une amélioration de 71% de réponse).[2]

One-by-one Processing[modifier | modifier le code]

La processus anti-pattern One-by-one est un cas particulier d'un anti-pattern global appellé Empty Semi Trucks.[10] Ce processus se produit quand un grand nombre de requêtes est nécessaire pour effectuer une tâche. L'exemple suivant montre un exemple de One-by-one processing:

OneByOne.java
...
for(Company c : companyList){
    for(Department d: c.getDepartment()){
        d.getDepartmentName();
    }
}
...

Le fichier OneByOne.java montre un programme qui parcourt l'ensemble des Companies (companyList), et récupère les noms de tous les departments pour chaque Company.

Si le fichier OneByOne.java a été exécuté en utilisant un ORM avec la configuration suivante :

Company.java
...
@OneToMany(mappedBy="company", fetch = Fetchtype.LAZY)
private List<Department> department;
...

Cela va générer un requête SQL pour sélectionner le département pour chaque objet Company.

Les requêtes SQL générées AVANT la correction:
select departement as d where d.companyID = 1;
select departement as d where d.companyID = 2;
...

Une manière de corriger ce point particulier est de changer la configuration de ORM dans la fichier Company.java, pour récupérer les objets départements dans la façon suivante :

Company.java
...
@OneToMany(mappedBy="company", fetch = Fetchtype.LAZY)
@BatchSize(size=50)
private List<Department> department;
...

Suite à l'addition d'un batch (Par exemple : @BatchSize(size=50)) à la variable instance département, l'ORM va sélectionner 50 objets départements pour chaque batch. La correction réduit significativement la nombre de requêtes SQL, et aide à améliorer la performance de la base de données.

Les requêtes SQL générées APRES la correction:
select departement as d where d.companyID in (1,2, ...);
...

N.B: la correction d'un problème One-by-one processing peut varier selon la situation et peut être parfois difficile (Par exemple: faire des update dans un boucle).

Pour montrer l'impact sur les performances, ce programme a été exécuté. Dans la base de données, il y a 300 enregistrements dans la table Company et 10 enregistrements Department pour chaque enregistrement de la table Company.

Le temps de réponse avant la correction est 1,68 secondes; corriger cet anti-pattern permet de réduire le temps de 1,39 seconde (une amélioration de 17% de réponse).[2]

Articles connexes[modifier | modifier le code]

Références[modifier | modifier le code]

  1. a et b « ORM Is an Offensive Anti-Pattern », sur yegor256 (consulté le )
  2. a b c d e f g et h (en) Tse-Hsun Chen, Weiyi Shang, Zhen Ming Jiang et Ahmed E. Hassan, « Detecting Performance Anti-patterns for Applications Developed Using Object-relational Mapping », Computer, ACM, iCSE 2014,‎ (ISBN 978-1-4503-2756-5, DOI 10.1145/2568225.2568259, lire en ligne)
  3. R. Johnson, « J2EE development frameworks », Computer, vol. 38,‎ , p. 107-110 (ISSN 0018-9162, DOI 10.1109/MC.2005.22, lire en ligne, consulté le )
  4. D. Barry et T. Stanienda, « Solving the Java object storage problem », Computer, vol. 31,‎ , p. 33-40 (ISSN 0018-9162, DOI 10.1109/2.730734, lire en ligne, consulté le )
  5. Neal Leavitt, « Whatever Happened to Object-Oriented Databases? », Computer, vol. 33,‎ , p. 16-19 (ISSN 0018-9162, DOI 10.1109/mc.2000.10067, lire en ligne, consulté le )
  6. a b c et d (en) Aditya Joshi, Object Relational Mapping in Comparison to Traditional Data Access Techniques, International Journal of Scientific & Engineering Research, (lire en ligne)
  7. « Hibernate Vs. JDBC ( A comparison) », sur www.java-samples.com (consulté le )
  8. « SysSurge: Developer tools for IT Professionals », sur www.syssurge.com (consulté le )
  9. Par Julien DUBOIS, « Improving the performance of the Spring-Petclinic sample... » (consulté le )
  10. (en) Performance Solutions: A Practical Guide to Creating Responsive, Scalable Software