Java Persistence API et Hibernate

Mise en place de l'Hibernate dans Spring

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Le projet qu'on développe va intéragir avec une couche modèle pour récupérer et mettre à jour les informations liées à la bibliothèque (emprunts, livres, souscriptions etc.). La base utilisée sera MySQL. Pour la persistance des objets dans cette base on employera le framework ORM, Hibernate.

On commencera cet article par expliquer les notions fondamentales incluses dans la partie "modèle" de notre exemple d'une application web avec Spring. Ensuite on se focalisera sur les cas d'implémentation.

Java Persistence API

Il s'agit d'une interface de programmation dont le but est d'aider les développeurs à organiser les données relationnelles au sein d'une application. Elle est basée sur 4 piliers :

  1. Les entités : elles représentent le modèle relationnelle de la base de données. En vulgarisant, on peut les considérer en tant que les tables de notre base. Cependant, les entités ne doivent pas limiter leurs formes à des colonnes spécifiés dans une table donnée. Grâce à un paramétrage qu'on verra plus loin, on peut définir des attributs supplémentaires qui ne seront pas pris en compte dans les requêtes.

    A part cela, on peut fidèlement copier le schéma de la table dans l'entité, en préservant ses relations avec d'autres tables ou les types de colonnes.
  2. Les persistence units : c'est le point de départ du moteur JPA. Il définit toutes les entités gérable par EntityManager ainsi que leur relation avec la base.

    Normalement, une liste des persistence units est définie dans un fichier nommé persistence.xml. Néanmoins, comme on le verra plus loin dans l'article, dans notre cas ce ne sera pas nécessaire. C'est Hibernate qui se chargera automatiquement d'aller chercher les entités gérables par EntityManager dans un endroit précis.
  3. L'entity manager : appelé aussi persistence manager, son rôle consiste à gérer le cycle de vie des entités.

    Représenté par l'implémentation de l'interface javax.persistence.EntityManager, il est associé au contexte de persistance (persistence context) qui regroupe toutes les entités disponibles.

    Chaque nouvelle instance d'une entité possède 1 des 4 états :
    - nouveau : instance ne possède pas d'identité et n'est pas encore associé au contexte de persistance
    - géré : instance possède déjà une identité et est attachée au contexte de persistance
    - détaché : instance possède une identité et n'est pas attachée au contexte de persistance
    - supprimé : instance avec une identité, attachée au contexte de persistance, mais marquée en tant que "supprimable" de la base de données
  4. Les transactions : ce sont elles qui englobent le travail de l'entity manager. Elles permettent de traiter les opérations de ce dernier d'une manière atomique. Cette façon de procéder garantit une cohérence de données. Les transactions se font avec des instructions SQL suivantes : BEGIN (pour commencer une transaction), COMMIT (pour valider toutes les opérations effectuées dans la transaction; elles sont alors sauvegardées dans la base) et ROLLBACK (pour annuler toutes les opérations effectuées dans la transactions; elles sont alors perdues).

Et Hibernate dans tout ça ?

Dans le paragraphe précédent on a pu constater que JPA joue le rôle d'une spécification. Elle définit donc la manière selon laquelle les objets Java interagissent avec une base de données relationnelle. On peut le constater en consultant la Javadoc du package javax.persistence. On y remarque la présence exclusive des interfaces.

En ce qui concerne Hibernate, il s'agit d'une implémentation du JPA. C'est elle qui fait tout le travail défini dans la spécification du JPA. Grâce à ce niveau d'abstraction supplémentaire, le développeur peut à tout moment changer l'implémentation du standard sans trop de soucis. Il faut juste avoir en tête le fait que chaque implémentation peut contenir des éléments (méthodes, classes) supplémentaires. Leur absence pendant le changement du fournisseur peut provoquer des problèmes d'instabilité de l'application.

Comment configurer Hibernate dans Spring ?

Notre application contient la configuration de l'Hibernate définie dans les fichiers XML. Le premier fichier, datasource.xml, précise les données de connexion qui seront utilisées par les gestionnaires des entités et des transactions. Voici le fichier en question :

        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
            destroy-method="close">
            <property name="driverClass" value="com.mysql.jdbc.Driver" />
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/library" />
            <property name="user" value="root" />
            <property name="password" value="root" />
        </bean>   

Le fichier qui va profiter de ces données d'accès s'appelle jpa-txt-config.xml. Il contient les définitions d'un transaction manager, un entity manager ainsi que des repositories. Le code ci-dessous contiendra uniquement les parties consacrées à la mise en place de l'Hibernate dans Spring. Cependant, toute la configuration du framework ORM a été omise. Elle est clairement expliquée dans la documentation Hibernate :

    <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>        
        <property name="packagesToScan" value="library.model.entity"/>
        <property name="jpaProperties">
            < -- list of Hibernate properties -->        
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        
        
    </bean>
    <jpa:repositories base-package="library.model.repository"
                      entity-manager-factory-ref="emf"
                      transaction-manager-ref="transactionManager"/>

Analysons le code ci-dessus tranquillement. Le premier bean, avec l'identifiant emf, représente l'entity manager. Il prend comme propriétés :
- l'instance du DataSource qui définit les informations obligatoires pour se connecter à la base de données
- l'implémentation du JpaVendorAdapter qui permet de déterminer le fournisseur pour entity manager
- depuis la version 3.1 du Spring, on n'est plus obligés de fournir le fichier persistence.xml définissant tous les persistence units gérables par entity manager. Désormais, on peut utiliser la propriété packagesToScan qui définit le nom du package contenant toutes les entités (marquées par l'annotation @Entity)
- la dernière propriété, jpaProperties, contient une liste des propriétés associées au fournisseur d'entity manager. Dans notre cas on utilise la balise <props /> qui contient des enfants <prop /> avec des propriétés. A la place de cela, on peut aussi employer une chaîne de caractères sous forme d'un fichier .properties qui serait parsé par PropertiesEditor

Concernant le bean transactionManager, il est utilisé pour gérer les transactions. Il s'agit donc d'un gestionnaire qui s'occupera de commencer, soumettre et d'annuler les transactions dans le code.

Le dernier élément n'a pas encore été présenté. Il s'agit des repositories qui permettent de gérer les requêtes à la base de données. Ils doivent implémenter l'interface JpaRepository faisant partie du projet Spring Data. Un repository représente une entité. L'interface de base (Repository) définit le type de l'entité ainsi que la clé étrangère à gérer. Elle peut contenir des méthodes qui, grâce à des requêtes écrites en Java Persistence Query Language (JPQL), permettront de récupérer les objets souhaités par le développeur. On verra cela en détail dans la partie consacrée à des repositories dans Spring.

Bartosz KONIECZNY Couche des données

Une question ? Une remarque ?

*

*

Un conseil Symfony1

Comment détecter si l'utilisateur est connecté ?

Dans notre fichier layout.php on contient une partie réservée aux utilisateurs connectés et non-connectés. Pour pouvoir détecter cela sans trop de complication, on devrait utiliser les slots. Le fichier d'action va contenir le framgent suivant en fonction du type de l'utilisateur (connecté ou pas) :

$this->getResponse()->setSlot('isConnected', 'true');
Alors dans le fichier layout.php on pourra utiliser ceci :
if(!has_slot('isConnected')  ) {
  // include login form
  include_partial('users/formLogin');
}
else {
  include_partial('users/accountBox');
}