Introduction aux ORM et JPA
Temps de lecture30 minRésumé
Un ORM (« Object Relational Mapping ») est une solution technique pour faire le lien entre le monde des logiciels orientés objet et celui des bases de données relationnelles. Ils sont aujourd’hui incontournables pour développer des applications qui manipulent des données stockées dans des bases de données relationnelles.
Plusieurs implémentations d’ORM existent pour différents langages : SQLAlchemy pour Python, Entity Framework (EF) pour C#, Doctrine pour PHP ou encore JPA pour Java.
Points essentiels
- Un ORM est une solution technique pour faciliter la programmation de l’accès aux données relationnelles dans une application orientée objet
- Aujourd’hui tout langage orienté objet à un (voir plusieurs) ORM
- En Java, JPA est une spécification d’ORM dont plusieurs implémentations (fournisseurs) existent
- L’utilisation d’un ORM masque l’utilisation d’un SGBDR pour stocker les données mais introduit de la complexité dans le code de l’application
Introduction
Un ORM (« Object Relational Mapping »), 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 librairie à utiliser dans une application : ç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)
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 que vous souhaitez stocker dans la base de données,
- pour récupérer dans un attribut d’un objet 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
d’une base de données et crée des objets User
correspondants :
-
Les lignes 1. à 5. permettent de se connecter à la base de données (
your_database_name
) d’un SGBDR PostgreSQL s’exécutant sur la machine locale (localhost
). Les login et mot de passe sont passés lors de la demande de connexion au SGBDR (ligne 5.) -
Les lignes 6. à 8. créent la reqûete SQL (un
SELECT
) et l’envoient au SGBDR. -
Les lignes 9. à 14. recupèrent le résultat de la requête (un objet de type
ResultSet
), initialisent les attributs d’un objetuser
pour chaque ligne recupérée et affichent sur le terminal l’objet créé. -
Les lignes 15. à 17. ferment la connexion au SGBDR.
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";
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
6. Statement statement = connection.createStatement();
// SQL query
7. String query = "SELECT id, name, email FROM users";
// Execute the query
8. ResultSet resultSet = statement.executeQuery(query);
// Process the result set
9. while (resultSet.next()) {
10. User user = new User();
11. user.setId(resultSet.getInt("id"));
12. user.setName(resultSet.getString("name"));
13. user.setEmail(resultSet.getString("email"));
// Display the data
14. System.out.println("User: " + user);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
// Close the resources
15. resultSet.close();
16. statement.close();
17. connection.close();
}
}
}
Bien que JDBC soit puissant et largement utilisé, il présente plusieurs inconvénients par rapport aux ORM :
-
JDBC nécessite beaucoup de code répétitif pour les tâches courantes telles que l’ouverture/la fermeture de connexions, la création d’instructions, la gestion des exceptions et le traitement des ensembles de résultats (
ResultSet
) -
Le code JDBC est sujet à des nombreuses erreurs : oublier de fermer les ressources (connexions, instructions, ensembles de résultats), ce qui peut entraîner des fuites de mémoire, gestion incorrecte des exceptions SQL, erreur dans l’écriture de requêtes SQL sous forme de chaînes de caractères …
-
JDBC fonctionne à bas niveau, ce qui oblige les développeurs à écrire du code SQL brut et à faire correspondre manuellement les lignes de la base de données à des objets Java.
Utiliser JPA
Une alternative moderne à JDBC est d’utiliser un ORM. Il existe des ORM pour quasiment tous les langages de programmation objet, il en existe donc pour Java.
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);
}
} catch(Exception e){
e.printStackTrace();
} finally {
// Close the EntityManager and EntityManagerFactory
entityManager.close();
entityManagerFactory.close();
}
}
}