Pages enfant
  • 3.3.4 Accès aux données avec JPA

Vous regardez une version antérieure (v. /wiki/pages/viewpage.action?pageId=100663544) de cette page.

afficher les différences afficher l'historique de la page

« Afficher la version précédente Vous regardez la version actuelle de cette page. (v. 7) afficher la version suivante »

A compléter

A revoir

Sommaire :


Par défaut, les applications esup-blank et esup-example sont configurées avec les gestionnaires de base de données s'appuyant sur Hibernate.

Les gestionnaires de bases de données

Le choix du gestionnaire de base de données Hibernate doit se faire en fonction de la maîtrise qu'a le développeur de la base de données. Deux cas se présentent :

  • Le développeur a la totale maîtrise de la base de données, c'est lui qui la fait évoluer en fonction des besoins de son application. Il utilisera dans ce cas l'implémentation UgradableHibernateDatabaseManagerImpl. Cette implémentation lui permettra de faire évoluer la structure de sa base de données en modifiant son mapping, de manière automatique sans même toucher au code SQL.
  • Le développeur n'a pas la maîtrise de la base de données : il s'agit d'une base de données institutionnelle, ou bien encore d'une base maîtrisée par une autre application. Il utilisera dans ce cas l'implémentation BasicHibernateDatabaseManagerImpl, et devra alors faire coller son mapping aux structures de la base de données.
    • Note : ne pas avoir la maîtrise de la base signifie que l'on ne fait que du select dessus, aucune mise à jour n'est possible.

On trouvera par exemple dans le fichier /properties/dao/dao.xml, pour une base maîtrisée par l'application :

<bean id="databaseManager1"
      class="[...].hibernate.UpgradableHibernateDatabaseManagerImpl" >
  <property name="sessionFactoryBeanName" value="sessionFactory" />
  <property name="createSessionFactoryBeanName" value="createSessionFactory" />
  <property name="upgradableSessionFactoryBeanName" value="upgradableSessionFactory" />
</bean>

Comme on le voit, on indique au gestionnaire de bases de données quelles sont les session factories (« usines à sessions ») à utiliser pour accéder à la base de données, en créer les structures et les mettre à jour.

Note : il s'agit bien des noms des beans des session factories, et non des références aux beans (car la simple instanciation de ces beans provoque la mise à jour de la structure de la base de données. Ils sont donc déclarés en lazy-init="true" et seront instanciés par l'application - via DatabaseUtils - et non par Spring).

La déclaration d'un gestionnaire de base de données dont l'application n'a pas la maîtrise se fera de la manière suivante :

<bean id="databaseManager2"
      class="[...].hibernate.BasicHibernateDatabaseManagerImpl" >
  <property name="sessionFactoryBeanName" value="sessionFactory" />
</bean>

Les session factories (« usines à session »)

Les trois beans sessionFactory, createSessionFactory et updateSessionFactory (le premier seulement si l'on ne maîtrise pas la structure de la base de données) héritent tous du même bean abstrait abstractHibernateSessionfactory :

<bean id="abstractHibernateSessionFactory"
      abstract="true"
      class="[...].orm.hibernate3.LocalSessionFactoryBean" >
  <property name="configLocation" value="classpath:/properties/dao/hibernate/hibernate.cfg.xml" />
  <property name="mappingLocations">
    <list>
      <value>
        classpath:/properties/dao/hibernate/mapping/Class1.hbm.xml
      </value>
      ...
    </list>
  </property>
</bean>

Cette déclaration indique que la configuration de l'accès physique à la base de données se trouve dans le fichier /properties/dao/hibernate/hibernate.cfg.xml, et que les mappings des classes se trouvent dans le répertoire /properties/dao/hibernate/mapping.

Le lecteur se reportera à la documentation Hibernate en ce qui concerne le contenu du fichier /properties/dao/hibernate/hibernate.cfg.xml.

Les beans sessionFactory, createSessionFactory et updateSessionFactory précisent simplement leur mode d'accès particulier (la propriété hibernateProperties surcharge les valeurs données dans le fichier /properties/dao/hibernate/hibernate.cfg.xml).

<bean id="sessionFactory"
      parent="abstractHibernateSessionFactory"
      lazy-init="true" >
</bean>

<bean  id="createSessionFactory"
       parent="abstractHibernateSessionFactory"
       lazy-init="true" >
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.hbm2ddl.auto">create</prop>
    </props>
  </property>
</bean>

<bean id="updateSessionFactory"
      parent="abstractHibernateSessionFactory"
      lazy-init="true" >
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.hbm2ddl.auto">update</prop>
    </props>
  </property>
</bean>

Les trois modes de Hibernate sont utilisés par esup-commons :

  • L'accès normal à la base de données s'appuie sur le bean sessionFactory (accès web et accès batch),
  • La création se fait en instanciant le bean createSessionFactory (via la tâche ant init-data),
  • La mise à jour se fait en instanciant le bean updateSessionFactory (via la tâche ant upgrade).

Mapping avec la base de données

Les fichiers de mapping sont dans le répertoire /properties/dao/hibernate/mapping et portent, par convention, l'extension .hbm.xml.

Voici ici l'exemple d'une classe Entry, qui appartient au package org.esupportail.formation.domain et a comme attributs id, value et date (de types long, java.lang.String et java.sql.Timestamp) :

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="org.esupportail.formation.domain.beans">
  <class name="Entry" table="d_entry">
    <id name="id" type="long">
      <column name="id"/>
      <generator class="native" />
    </id>
    <property name="value" type="string">
      <column name="valu" length="250"
              not-null="true" unique="true"/>
    </property>
    <property name="date" type="timestamp">
      <column name="dat" not-null="true"/>
    </property>
  </class>
</hibernate-mapping>

Dans l'exemple, id est la clé primaire dans la table. id est dissocié de value qui a aussi une contrainte d'unicité dans la base de données. C'est une des bonnes pratiques pour Hibernate que de ne donner aucune notion métier à la clé primaire en base pour garantir l'évolutivité de l'application.

Si le schéma de votre base de données est déjà défini, vous pouvez générer automatiquement avec Eclipse les fichiers de mapping Hibernate et les POJOs (Plain Old Java Objects) dont vous avez besoin. Cette opération vous fera gagner énormément de temps de développement et d'erreurs.

Pour cela, dans Eclipse, après avoir correctement paramétré le fichier hibernate.cfg.xml, faire Window => Open Perspective.
Dans la vue Hibernate Configurations de cette perspective, cliquer sur le bouton "+" pour définir une nouvelle configuration. Renseigner les champs en s'inspirant l'exemple suivant :

Pour initialiser la génération des fichiers Hibernate, faire Run > Hibernate Code Generation... > Open Hibernate Code Generation... Dialog... et renseigner à nouveau les champs en s'inspirant l'exemple suivant :


 

Il ne vous reste plus qu'à cliquer sur le bouton Run.
Vous trouverez tous les fichiers générés dans le répertoire indiqué dans le champ Output Directory de l'onglet Main (cf ci-dessus). N'oubliez pas quand même :

- de déplacer les fichiers de mappings Hibernate générés (fichiers *.hbm.xml) dans le répertoire esup-blank/properties/dao/hibernate/mapping !

- de rajouter dans le fichier dao.xml chaque fichier de mapping généré en ajoutant, par exemple pour une entité "Population", la ligne :

    <value>classpath:/properties/dao/hibernate/mapping/Population.hbm.xml</value>
Une fois ce travail effectué, vous pouvez directement lancer la target init-data du fichier build-devel.xml !

Utilisation de HQL

HQL est un langage d'interrogation de base de données de Hibernate. Il est orienté objet et a une forme proche du SQL mais travaille sur les objets Java définis dans vos fichiers de mapping et pas sur des noms de tables de votre base de données.

Note : Hibernate propose aussi une autre méthode d'interrogation, dite « requêtes par critères ». Il s'agit d'une API et plus d'un langage comme HQL. Cette dernière n'est pas abordée dans ce document.
Contrairement à SQL, le mot clé SELECT de HQL n'est pas obligatoire. S'il n'est pas utilisé ce sont des objets qui sont retournés. S'il est utilisé il est possible de seulement retourner les propriétés de ces objets.

Après le mot-clé FROM on n'utilise pas de noms de tables mais des noms de classes. Ce nom de classe est sensible à la casse comme en Java. Le nom du package n'est pas obligatoire car Hibernate a un mécanisme d'auto-import.

Exemple de requête HQL simple qui récupère toutes les instances de la classe Thing dans la base de données :

"FROM Thing"

Avec HQL il est possible de faire des jointures (mot clé JOIN), d'utiliser le mot clé WHERE afin de limiter les objets à retourner par la requête. Il est aussi possible d'utiliser des fonctions d'agrégation (sum(...), count( * )) et des expressions (upper(), +, between) dans les requêtes HQL. De même, le support de sous-requêtes et des clauses ORDER BY et GROUP BY est offert.

Pour plus de renseignements sur la construction des requêtes HQL, le lecteur se reportera à la documentation de Hibernate.

Dans esup-commons HQL est notamment utilisé pour :

  • Compter le nombre d'instances persistantes d'une classe (nombre de lignes dans une table) avec la méthode getQueryIntResult. Par exemple :
    getQueryIntResult("select count(*) from Entry");
    
  • Sélectionner les objets à faire apparaître dans un paginateur Hibernate.

Parmi les autres recommandations pour Hibernate on trouvera le fait de devoir surcharger les méthodes hashCode() et equals(), par exemple :

public boolean equals(final Object obj) {
  if (obj == null) {
    return false;
  }
  if (!(obj instanceof Entry)) {
    return false;
  }
  return id == ((Entry) obj).getId();
}

public int hashCode() {
  return super.hashCode();
}
Exercice : Modifier le mapping Hibernate Afficher l'énoncéCacher l'énoncé

Écrire la classe Entry. Ecrire le fichier de mapping correspondant et le référencer depuis dao.xml. Exécuter la tâche ant init-data et vérifier que la table correspondante a bien été créée dans la base de données.

Afficher la solutionCacher la solution

Créer le fichier Entry.hbm.xml en prenant modèle sur une fichier existant :

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="org.esupportail.formation.domain.beans">
  <class name="Entry" table="d_entry">
    <id name="id" type="long">
          <column name="id"/>
          <generator class="native" />
    </id>
    <property name="value" type="string">
          <column name="valu" length="250" not-null="true" unique="true"/>
    </property>
    <property name="date" type="timestamp">
          <column name="dat" not-null="true"/>
    </property>
  </class>
</hibernate-mapping>

 Ajouter la référence au fichier Entry.hbm.xml dans le fichier properties/dao/dao.xml

<property name="mappingLocations">
  <list>
    <value>
      classpath:/properties/dao/hibernate/mapping/Entry.hbm.xml
    </value>
.../...
  </list>
</property>

Ecrire ensuite la classe Entry dans le package xxx.domain.beans :

import java.sql.Timestamp;

Class Entry {

 private long id;

 private String value;

 private Timestamp date;

 public Entry() {}

 public boolean equals(final Object obj) {
 if (obj == null) { return false; }
   if (!(obj instanceof Entry)) { return false; }
   return id == ((Entry) obj).getId();
 }

 /* getters and setters (OBLIGATOIRE pour Hibernate !!!) */
}

Comment ça marche

Comme vu plus haut, les points d'entrée de esup-commons s'assurent du respect du modèle one-session-per-request.

Lors de l'ouverture d'une session Hibernate à une base de données, on appelle le code suivant :

TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));

Cela associe la session créée (session) à l'usine à sessions (sessionFactory) qui l'a créée, pour le thread courant.

Pour accéder aux données, le service d'accès aux données HibernateDaoService utilise la méthode getHibernateTemplate() (héritée de la classe HibernateDaoSupport). Cette méthode getHibernateTemplate() récupère la session courante dans les données du thread courant à partir de l'usine à session (sessionFactory), injectée dans le bean daoService par Spring.

Ce mécanisme ingénieux permet d'éviter toute adhérence entre le service métier et le service d'accès aux données. La classe statique DatabaseUtils permet de réduire toute adhérence entre les points d'entrée de esup-commons et les gestionnaires de données utilisés.

  • Aucune étiquette