.. _chap-introjpa: ############################### Introduction à JPA et Hibernate ############################### Nous avons donc un premier aperçu de JDBC, et la démarche logique que nous avons suivie en cherchant à respecter les principes d'indépendance dictés par notre approche MVC nous a mené à "encapsuler" les lignes d'une table relationnelle sous forme d'objet avec d'obtenir une intégration naturelle à l'application. Cela revient à isoler les accès à la base dans la couche modèle, de manière à ce que ces accès deviennent transparents pour le reste de l'application. À ce stade les avantages devraient déjà vous être perceptibles: - possibilité de faire évoluer la couche d'accès aux données indépendamment du reste de l'application; - possibilité de tester séparément chaque couche, et même la couche modèle sans avoir à se connecter à la base (par création d'entités non persistantes). Si ce n'est pas encore clair, j'espère que cela le deviendra. Au passage, on peut souligner qu'il est tout à fait possible de développer une application valide sans recourir à ces règles de conception qui peuvent sembler lourdes et peu justifiées. C'est possible mais cela devient de moins en moins facile au fur et à mesure que l'application grossit ainsi que les équipes en charge de la développer. ***************************** S1: Concepts et mise en route ***************************** Supports complémentaires : * `Diapos pour la session "S1 : Concepts et mise en route" `_ * Vidéo associée : https://mediaserver.cnam.fr/permalink/v125f3594bcf65g3xaes/ .. * Vidéo associée : https://avc.cnam.fr/univ-r_av/avc/courseaccess?id=1982 Il est temps de clarifier cette notion de représentations incompatibles entre la base de données et une application objet. Le constat de cette incompatibilité justifie le développement des outils ORM, et il est bien utile de la maîtriser pour comprendre le but de ces outils. Relationnel et objet ==================== Admettons donc que nous avons décidé d'adopter cette approche systématique de séparation en couches, avec tout en bas une couche d'accès aux données basée sur une représentation objet. .. admonition:: Rôle d'un système ORM Le rôle d'un système ORM est de convertir *automatiquement*, *à la demande*, la base de données sous forme d'un *graphe d'objet*. L'ORM s'appuie pour cela sur une *configuration* associant les *classes* du modèle fonctionnel et le schéma de la base de donnés. L'ORM génère des requêtes SQL qui permettent de matérialiser ce graphe ou une partie de ce graphe en fonction des besoins. Pour bien comprendre cette notion de graphe et en quoi elle diffère de la représentation dans la base relationnelle, voici une petit échantillon de cette dernière. .. _table-films: .. list-table:: Table des films :header-rows: 1 * - id - titre - année - idRéalisateur * - 17 - Pulp Fiction - 1994 - 37 * - 54 - Le Parrain - 1972 - 64 * - 57 - Jackie Brown - 1997 - 37 .. _table-artistes: .. list-table:: Table des artistes :header-rows: 1 * - id - nom - prénom - année_naissance * - 1 - Coppola - Sofia - 1971 * - 37 - Tarantino - Quentin - 1963 * - 64 - Coppola - Francis - 1939 Ce petit exemple illustre bien comment on représente une association en relationnel. C'est un mécanisme de référence *par valeur* (et pas par adresse), où la clé primaire d'une entité (ligne) dans une table est utilisée comme attribut (dit *clé étrangère*) d'une autre entité (ligne). Ici, le réalisateur d'un film est un artiste dans la table ``Artiste``, identifié par une clé nommée ``id``. Pour faire référence à cet artiste dans la table ``Film``, on ajoute un attribut *clé étrangère* ``idRealisateur`` dont la valeur est l'identifiant de l'artiste. Notez dans l'exemple ci-dessus que cet identifiant est 37 pour *Pulp Fiction* et *Jackie Brown*, 64 pour *Le parrain*, avec la correspondance à une et une seule ligne dans la table ``Artiste``. Voyons maintenant la représentation équivalente dans un langage objet en général (et donc java en particulier). Nous avons des objets, et la capacité à référencer un objet depuis un autre objet (cette fois par un système d'adressage). Par exemple, en supposant que nous avons une classe ``Artiste`` et une classe ``Film`` en java, le fait qu'un film ait un réalisateur se représente par une référence à un objet ``Artiste`` sous forme d'une propriété de la classe ``Film``. .. code-block:: java class Film { (...) Artiste realisateur; (...) } Et du côté de la classe ``Artiste``, nous pouvons représenter les films réalisés par un artiste par un ensemble. .. code-block:: java class Artiste { (...) Set filmsDiriges; (...) } .. important:: On peut noter dès maintenant qu'une différence importante entre les associations en relationnel et en java est que les premières sont *bi-directionnelles*. Il est toujours possible par une requête SQL (une jointure) de trouver les films réalisés par un artiste, ou le réalisateur d'un film. En java, le lien peut être représenté de chaque côté de l'association, ou des deux. Par exemple, on pourrait mettre la propriété ``réalisateur`` dans la classe ``Film``, mais pas ``filmsDiriges`` dans la classe ``Artiste``, ou l'inverse, ou les deux. Cette subtilité est la source de quelques options plus ou moins obscures dans les systèmes ORM, nous y reviendrons. La base de données, transformée en java, se présentera donc sous la forme d'un graphe d'objet comme celui montré par la figure :ref:`graphe-base`. .. _graphe-base: .. figure:: ../figures/graphe-base.png :width: 70% :align: center Le graphe d'objets Java Lisez et relisez cette section jusqu'à ce qu'elle soit limpide (et sollicitez vos enseignants si elle ne le devient pas). Les notions présentées ci-dessous sont à la base de tout ce que nous allons voir dans les prochains chapitres. La couche ORM ============= Pour bien faire, il nous faut une approche systématique. On peut par exemple décréter que: - on crée une classe pour chaque entité; - on équipe cette classe avec des méthodes *create()*, *get()*, *delete()*, *search()*, ..., que l'on implante en SQL/JDBC; - on implante la navigation d'une entité à une autre, à l'aide de requête SQL codées dans les classes et exécutées en JDBC. Il ne serait pas trop difficile (mais pas trop agréable non plus) de faire quelques essais selon cette approche. Par exemple la méthode *get(id)* est clairement implantée par un ``SELECT * From WHERE id=:id``. Le *create()* est implanté par un ``INSERT INTO ()``, etc. Nous ne le ferons pas pour au moins trois raisons: - concevoir nous-mêmes un tel mécanisme est un moyen très sûr de commettre des erreurs de conception qui se payent très cher une fois que des milliers de lignes de code ont été produites et qu'il faut tout revoir; - la tâche répétitive de produire des requêtes toujours semblables nous inciterait rapidement à chercher un moyen automatisé; - et enfin - et surtout - ce mécanisme dit *d'association objet-relationnel* (ORM pour *Objet-relational mapping*) a été mis au point, implanté, et validé depuis de longues années, et nous disposons maintenant d'outils matures pour ne pas avoir à tout inventer/développer nous-mêmes. Non seulement de tels outils existent, mais de plus ils sont normalisés dans la plate-forme java, sous le nom générique de *Java Persistence API* ou JPA. JPA est essentiellement une spécification intégrée au JEE qui vise à standardiser la couche d'association entre une base relationnelle et une application Java construite sur des objets (à partir de maintenant nous appelerons cette couche ORM). Le rôle de la couche ORM est illustré par la figure :ref:`mapping`. .. _mapping: .. figure:: ../figures/mapping.png :width: 60% :align: center Le *mapping* d'une base relationnelle en graphe d'objets java Il est important (ou du moins utile) de signaler que cette standardisation vient après l'émergence de plusieurs *frameworks* qui ont exploré les meilleures pratiques pour la réalisation d'un système ORM. Le plus connu est sans doute Hibernate, que nous allons utiliser dans ce qui suit (voir http://fr.wikipedia.org/wiki/Mapping_objet-relationnel pour une liste). Avec l'apparition de JPA, ces frameworks vont tendre à devenir des *implantations* particulières de la norme (rappelons que JPA est une spécification visant à la standardisation). Comme JPA est une sorte de facteur commun, il s'avère que chaque *framework* propose des extensions spécifiques, souvent très utiles. Il existe une implantation de référence de JPA, `EclipseLink `_. JPA définit une interface dans le package ``javax.persistence.*``. La définition des associations avec la base de données s'appuie essentiellement sur les annotations Java, ce qui évite de produire de longs fichiers de configuration XML qui doivent être maintenus en parallèle aux fichiers de code. On obtient une manière assez légère de définir une sorte de base de données objet *virtuelle* qui est matérialisée au cours de l'exécution d'une application par des requêtes SQL produites par le *framework* sous-jacent. Le choix que j'ai fait dans ce qui suit est d'utiliser Hibernate comme *framework* de persistance, et de respecter JPA autant que possible pour que vous puissiez passer à un autre framework sans problème (en particulier, les annotations sont utilisées systématiquement). Dans ce chapitre nous allons effectuer nos premier pas avec JPA/Hibernate, avec pour objectif de construire une première couche ORM pour notre base *webscope*. L'approche utilisée est assez simple, elle nous servira de base de départ pour découvrir progressivement des concepts plus avancés. Dernière chose: les ORM s'appuient sur JDBC et nous allons donc retrouver certains aspects déjà connus (et en découvrir d'autres). .. admonition:: Exercice: comprendre la représentation par graphe Prenez les deux films suivants: *Impitoyable* et *Seven*, avec leurs acteurs et metteurs en scène, et construisez le graphe d'objets. ******************************* S2: Premiers pas avec Hibernate ******************************* Supports complémentaires : * `Diapos pour la session "S2 : Premiers pas avec Hibernate" `_ * Vidéo associée : https://mediaserver.cnam.fr/permalink/v125f3594bdb82xlnti5/ .. * Vidéo associée : https://avc.cnam.fr/univ-r_av/avc/courseaccess?id=1983 Installation ============ Vous allez installer Hibernate dans votre environnement, en commençant par récupérer la dernière version stable sur le site `Hibernate `_. À l'heure où j'écris, cette version est la 5.4, les exemples connées dans ce qui suit ne devraient pas dépendre, sauf sur des points mineurs, de la version. .. _hibernate_releasepage: .. figure:: ../figures/hibernate_releasepage.png :width: 70% :align: center La page de téléchargement d'Hibernate (cliquez sur le bouton dans la zone violette) Dans le répertoire ``hibernate`` obtenu après décompression, vous trouverez une documentation complète et les librairies, dans ``lib``. Elles sont groupées en plusieurs catégories correspondant à autant de sous-répertoires (notez par exemple celui nommé ``jpa``). Pour l'instant nous avons besoin de toutes les librairies dans ``required``: copiez-les, comme d'habitude, dans le répertoire ``WEB-INF/lib`` de votre application Web. Le pilote MySQL doit bien entendu rester en place: il est utilisé par Hibernate. Utilisez l'option ``refresh`` sous Eclipse sur votre projet (clic bouton droit) pour que ces nouvelles librairies soient identifiées. Un des objets essentiels dans une application Hibernate est l'objet ``Session`` qui est utilisé pour communiquer avec la base de données. Il peut être vu comme une généralisation/extension de l'objet ``Connection`` de JDBC. Une session est créée à partir d'un ensemble de paramètres (dont les identifiants de connexion JDBC) contenus dans un fichier de configuration XML nommé ``hibernate.cfg.xml``. .. important:: Pour que ce fichier (et toute modification affectant ce fichier) soit automatiquement déployé par Tomcat, il doit être placé dans un répertoire ``WEB-INF/classes``. Créez ce répertoire s'il n'existe pas. Voici le fichier de configuration minimal avec lequel nous débutons. .. code-block:: xml jdbc:mysql://localhost:3306/webscope com.mysql.jdbc.Driver orm orm 10 org.hibernate.dialect.MySQLInnoDBDialect true org.hibernate.cache.NoCacheProvider false On retrouve les paramètres qui vont permettre à Hibernate d'instancier une connexion à MySQL avec JDBC (mettez vos propres valeurs bien sûr). Par ailleurs chaque système relationnel propose quelques variantes du langage SQL qui vont être prises en compte par Hibernate: on indique le dialecte à utiliser. Finalement, les derniers paramètres ne sont pas indispendables mais vont faciliter notre découverte, notamment avec l'option ``show_sql`` qui affiche les requêtes SQL générées dans la console java. Il peut y avoir plusieurs ```` dans un même fichier de configuration, pour plusieurs bases de données, éventuellement dans plusieurs serveurs différents. Test de la connexion ==================== Comme pour JDBC, nous créons - un contrôleur ``Hibernate.java`` associé à l'URL ``/hibernate``. Il devrait avoir grosso modo la même structure que celui utilisé pour JDBC puisque nous allons reproduire les mêmes actions; - une liste d'actions implantés dans une classe de tests nommée ``TestsHibernate.java``; - des vues placées dans ``WebContent/vues/hibernate``. Nous commençons par tester que notre paramétrage est correct. Voici le squelette de ``TestsHibernate.java``. .. code-block:: java package modeles; import org.hibernate.Session; public class TestsHibernate { /** * Objet Session de Hibernate */ private Session session; /** * Constructeur établissant une connexion avec Hibernate */ public TestsHibernate() { Configuration configuration = new Configuration().configure("/hibernate.cfg.xml"); ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build(); SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry); session = sessionFactory.openSession(); } } Pour créer une connexion avec Hibernate nous passons par une ``SessionFactory`` qui repose sur le paramétrage de notre fichier de configuration (ce fichier lui-même est chargé dans un objet ``Configuration``). Si tout se passe bien, on obtient un objet par lequel on peut ouvrir/fermer des sessions. Plusieurs raisons peuvent faire que ça ne marche pas (au moins du premier coup). - le fichier de configuration n'est pas trouvé (il doit être dans le ``CLASSPATH`` de l'application); - ce fichier contient des erreurs; - les paramètres de connexion sont faux; - et toutes sortes d'erreurs étranges qui peuvent nécessiter le redémarrage de Tomcat.. En cas de problème une exception sera levée avec un message plus ou moins explicite. Testons directement cette connexion. Voici l'action de connexion: .. code-block:: java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { TestsHibernate tstHiber = new TestsHibernate(); String maVue = VUES + "connexion.jsp"; RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(maVue); dispatcher.forward(request, response); } Si cela fonctionne du premier coup, bravo, sinon cherchez l'erreur (avec notre aide bienveillante si nécessaire). On y arrive toujours. Ma première entité ================== Dans JPA, on associe une table à une classe Java annotée par ``@Entity``. Chaque ligne de la table devient une *entité*, instance de cette classe. La classe doit obéir à certaines contraintes qui sont à peu près comparables à celle d'un JavaBean. Les propriétés sont privées, et des accesseurs *set* et *get* sont définis pour chaque propriété. L'association d'une propriété à une colonne de la table est indiquée par des annotations. L'exemple qui suit est plus parlant que de longs discours: .. code-block:: java package modeles.webscope; import javax.persistence.*; @Entity public class Pays { @Id String code; public void setCode(String c) {code = c;} public String getCode() {return code ;} @Column String nom; public void setNom(String n) {nom = n;} public String getNom() {return nom;} @Column String langue; public void setLangue(String l) {langue = l;} public String getLangue() {return langue;} } Remarquez l'import du *package* ``javax.persistence``, et les annotations suivantes: - ``@Entity``: indique que les instances de la classe sont *persistantes* (stockées dans la base); - ``@Id``: indique que cette propriété est la clé primaire; - ``@Column``: indique que la propriété est associée à une colonne de la table. Et c'est tout. Bien entendu les annotations peuvent être beaucoup plus complexes. Ici, on s'appuie sur des choix par défaut: le nom de la table est le même que le nom de la classe; idem pour les propriétés, et l'identifiant n'est pas auto-généré. Cela nous suffit pour un premier essai. Il reste à déclarer dans le fichier de configuration que cette entité persistante est dans notre base: .. code-block:: xml (...) Hibernate ne connaitra le *mapping* entre une classe et une table que si elle est déclarée dans la configuration. Une autre solution pour déclarer une classe persistante est de la charger explicitement dans la configuration. .. code-block:: java public TestsHibernate() { Configuration configuration = new Configuration().configure(); // ICI ON AJOUTE LES CLASSES JPA configuration.addAnnotatedClass(Pays.class); // FIN DE L'AJOUT DES CLASSES JPA ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() .applySettings(configuration.getProperties()).build(); SessionFactory sessionFactory = configuration .buildSessionFactory(serviceRegistry); session = sessionFactory.openSession(); } À vous de choisir. Notez que l'absence de déclaration d'un *mapping* entraîne une exception ``Unknown Entity``. La déclaration de l'entité avec l'une des deux méthodes ci-dessus doit régler le problème. .. important:: Depuis la version 5, il semble qu'Hibernate ne prenne plus en compte les instructions du document de configuration XML. *Il est donc impératif d'ajouter les classes persistantes au moment du chargement de la configuration, comme indiqué ci-dessus.* Insertion d'une entité ====================== Maintenant définissez une action ``insertion`` dans votre contrôleur, avec le code suivant. .. code-block:: java TestsHibernate tstHiber = new TestsHibernate(); Pays monPays = new Pays(); monPays.setCode("is"); monPays.setNom("Islande"); monPays.setLangue("islandais"); tstHiber.insertPays(monPays); maVue = VUES + "insertion.jsp"; Vous voyez que l'on instancie et utilise un objet ``Pays`` comme n'importe quel *bean*. La capacité de l'objet est devenir persistant n'apparaît pas du tout. On peut l'utiliser comme un objet "métier", *transient* (donc non sauvegardé dans la base). Pour le sauvergarder on appelle la méthode ``insertPays`` de notre classe utilitaire, que voici. .. code-block:: java session.beginTransaction(); session.save(pays); session.getTransaction().commit(); Il suffit donc de demander à la session de sauvegarder l'objet (dans le cadre d'une transaction) et le tour est joué. Hibernate se charge de tout: génération de l'ordre SQL correct, exécution de cet ordre, validation. En appliquant ce code vous devriez obtenir dans la console java l'affichage de la requête SQL. Vous pouvez vérifier avec phpMyAdmin que l'insertion s'est bien faite. .. _ex-hiber-insert: .. admonition:: Exercice: associer un formulaire de saisie, et une vue Il s'agit de compléter notre première fonction d'insertion en créant une formlaire pour saisir les paramètres d'un pays. La validation de ce formulaire doit déclencher l'insertion dans la base et afficher une vue donnant les valeurs affichées. Lecture de données ================== Voyons maintenant comment lire des données de la base. Nous avons plusieurs possibilités. - *Transmettre une requête SQL via Hibernate.* C'est la moins portable des solutions car on ne peut pas être sûr à 100% que la syntaxe est compatible d'un SGBD à un autre; il est vrai qu'on ne change pas de SGBD tous les jours. - *Transmettre une requête HQL*. Hibernate propose un langage d'interrogation proche de SQL qui est transcrit ensuite dans le dialecte du SGBD utilisé. - *Utiliser l'API* ``Criteria``. Plus de langage externe (SQL) passé plus ou moins comme une chaîne de caractères: on construit une requête comme un objet. Cette interface a également l'avantage d'être normalisée en JPA. .. note:: JPA définit un langage de requête, JPQL (*Java Persistence Query Language*) qui est un sous-ensemble de HQL. Toute requête JPQL est une requête HQL, l'inverse n'est pas vrai. Passons à la pratique. Voici la requête qui parcours la table des pays, avec l'API ``Criteria``. .. code-block:: java public List lecturePays() { // Create CriteriaBuilder CriteriaBuilder builder = session.getCriteriaBuilder(); // Create CriteriaQuery CriteriaQuery criteria = builder.createQuery(Pays.class); criteria.from(Pays.class); // Execute query List pays = session.createQuery(criteria).getResultList(); return pays; } La requête était assez simple avec des versions antérieures de Criteria, mais elle reste assez lisible : on crée un constructeur de requêtes, on lui donne une requête avec une classe Java (ici Pays), et l'on récupère la liste des résultats dans une liste après exécution. Bien sûr pour des requêtes plus complexes, la construction est plus longue. Voici la méthode équivalente en HQL, le langage associé à Hibernate, que nous explorerons en détail dans le chapitre :ref:`chap-hql`. .. code-block:: java public List lecturePaysHQL() { Query query = session.createQuery("from Pays"); return query.list(); } C'est presque la même chose, mais vous voyez ici que l'on introduit une chaîne de caractères contenant une expression syntaxique qui n'est pas du java (et qui n'est donc pas contrôlée à la compilation). .. _ex-hiber-scan: .. admonition:: Exercice: afficher la liste des pays À vous de jouer: créez l'action, la vue et le modèle pour afficher la liste des pays en testant les deux versions ci-dessus. Gérer les associations ====================== Et nous concluons cette introduction avec un des aspects les plus importants du *mapping* entre la base de données et les objets java: la représentation des associations. Pour l'instant nous nous contentons de l'association (plusieurs à un) entre les films et les pays ("*plusieurs* films peuvent avoir été tournés dans *un seul* pays"). En relationnel, nous avons donc dans la table ``Film`` un attribut ``code_pays`` qui sert de clé étrangère. Voici comment on représente cette association avec Hibernate. .. code-block:: java package modeles.webscope; import javax.persistence.*; @Entity public class Film { @Id private Integer id; public void setId(Integer i) {id = i;} @Column String titre; public void setTitre(String t) {titre= t;} public String getTitre() {return titre;} @Column Integer annee; public void setAnnee(Integer a) {annee = a;} public Integer getAnnee() {return annee;} @ManyToOne @JoinColumn (name="code_pays") Pays pays; public void setPays(Pays p) {pays = p;} public Pays getPays() {return pays;} } Cette classe est incomplète: il manque le genre, le réalisateur, etc. Ce qui nous intéresse c'est le lien avec ``Pays`` qui est représenté ici: .. code-block:: java @ManyToOne @JoinColumn (name="code_pays") Pays pays; public void setPays(Pays p) {pays = p;} public Pays getPays() {return pays;} On découvre une nouvelle annotation, ``@ManyToOne``, qui indique à Hibernate que la propriété ``pays``, instance de la classe ``Pays``, encode un des côtés d'une association plusieurs-à-un entre les films et les pays. Pour instancier le pays dans lequel un film a été tourné, Hibernate devra donc exécuter la requête SQL qui, connaissant un film, permet de trouver le pays associé. Cette requête est: .. code-block:: sql select * from Pays where code = :film.code-pays La *clé étrangère* qui permet de trouver le pays est ``code_pays`` dans la table ``Film``. C'est ce qu'indique la seconde annotation, ``@JoinColumn``. .. note:: N'oubliez pas de modifier votre fichier de configuration pour indiquer que vous avez défini une nouvelle classe "mappée". Prenez le temps de bien réfléchir pour vous convaincre que toutes les informations nécessaires à la constitution du lien objet entre une instance de la classe ``Film`` et une instance de la classe ``Pays`` sont là. Pour le dire autrement: toutes les informations permettant à Hibernate d'engendrer la requête qui précède ont bien été spécifiées par les annotations. .. _ex-hiber-films-pays: .. admonition:: Exercice: afficher la liste des films et leur pays À vous de jouer: créez une action ``listeFilms`` qui affiche la liste des films et le pays où ils ont été tournés. Dans la JSTL, l'affichage du pays ne devrait pas être plus compliqué que:: ${film.pays.nom} Au passage regardez dans la console les requêtes transmises par Hibernate: instructif. .. important:: Vous noterez que nous avons défini l'association du côté ``Film`` mais *pas* du côté ``Pays``. Etant donné un objet pays, nous de pouvons pas accéder à la liste des films qui y ont été tournés. *L'association est uni-directionnelle*. Gardez cela en mémoire mais ne vous grillez pas les neurones dessus: nous allons y revenir. Vous devriez maintenant pouvoir implanter avec Hibernate l'action qui affiche la liste des films avec leur metteur en scène. .. _ex-hiber-films: .. admonition:: Exercice: afficher la liste des films et leur metteur en scène Aide: il faut définir la classe ``Artiste``, mappée, et la lier à ``Film`` pour représenter l'association. Je vous aide: dans une approche "graphe d'objet", le metteur en scène est un objet propriété de ``Film``. À l'execution, examinez les requêtes SQL produites par Hibernate et méditez sur ce que cela implique. Prenez également le recul pour apprécier ce qu'apporte Hibernate par rapport à la gestion manuelle que nous avons envisagée dans le chapitre précédent. ************************* Résumé: savoir et retenir ************************* Les notions suivantes devraient être claires maintenant: - une application objet représente les données comme un *graphe d'objet*, liés par des références; - une couche ORM transforme une base relationnelle en graphe d'objets et permet à l'application de *naviguer* dans ce graphe en suivant les références entre objets; - la transformation est effectuée à la volée en fonction des navigations (accès) effectués par l'application; elle repose sur la génération de requêtes SQL; Hibernate est une implantation de la spécification JPA, et un peu plus que cela.