mercredi 16 mars 2011

Software : Quel ORM ? Hibernate

Incorporation d'Hibernate dans le projet.



Pour l'installation, rien de plus facile, ajout du Snippet dans le pom.xml (voir Post sur Maven)

Rappel : Hibernate est l'un des nombreux ORM disponibles (Object Relation Mapping) il va permettre de faire le lien entre nos objets JAVA et les tables de la base de données MYSQL. Et ainsi par la suite nous apporter nombre fonctionnalités de requêtes/ recherches / insert, simplifiées. Il gérera également 2 caches permettant d'accélérer les traitements. il sera également possible de persister notre héritage, faire des associations 1-n, n-n, n-1, projections... On aurait pu utiliser le simplissime DB4O (Base de données purement Objet), mais bon l'envie d'un MYSQL .... 

Le lien pour les curieux  http://www.hibernate.org/ & http://www.db4o.com/

Bon, Maven a fait le travail pour nous (n'oubliez pas d'ajouter la dépendance avec Mysql pour le driver Jdbc)

Il y a donc comme tout bon framework un fichier de configuration appelé hibernate.cfg.xml. Il permet (en résumé) d'indiquer les identifiants de connection à la base de données, le driver utilisé*, le dialect à utiliser (Mysql, Oracle, DB2, ....). ainsi que des liens vers nos objets à persister.


* Hibernate n'est pas trés bavard pour les requêtes qu'il passe, on voit les requetes (dans le format hql Hibernate Query Language), mais les valeurs ne sont pas visibles. Pour combler cela on peut utiliser un log4jdbc ou encore p6spy qui s'intercale entre le drivers JDBC et Hibernate, il suffira de préciser dans la property <property name="connection.driver_class">com.mysql.jdbc.Driver</property>

Voici le mien : 

Un truc bien pratique (en vert pâle) la property hibernate.hbm2ddl.auto positionnée à update permet à hibernate de créer la table sur la base de définition de mapping de notre objet, pratique lorsque l'on part d'une base vide, cela évite de se taper un DDL de création.
En bleu, un premier mapping, celui des paramètres de l'application (répertoires, valeurs par défaut, ....) et oui faut bien les persister.


<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>


<session-factory>
<property name="connection.url">jdbc:mysql://localhost/homeautomate</property>
<property name="connection.username">homeautomate</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="connection.password">homeautomate</property>
<property name="hibernate.hbm2ddl.auto">update</property>


 <property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
    <!--  thread is the short name for
      org.hibernate.context.ThreadLocalSessionContext
      and let Hibernate bind the session automatically to the thread
    -->
    <property name="current_session_context_class">thread</property>
    
    <!-- this will show us all sql statements -->
    <property name="hibernate.show_sql">true</property>


<!-- mapping files -->
<mapping resource="test/Parameter.hbm.xml" />


</session-factory>


</hibernate-configuration>




Vouala pour le fichier de configuration, jetons un oeil à notre Parameter.hbm.xml
On indique en rouge le package où se trouve la classe à mapper. En vert le nom de notre Objet (ici Parameter)
Toujours un id (ici purement technique et non fontionnel) il identifiera notre objet (Attention à ce propos, il faudra redéfinir l'equals et le hashcode dans notre objet afin de tenir de cet id, Hibernate l'utilisant pour son cache et différencier les objets).
Generator Class précise comment est géré l'ID (ici un simple incrément, on aurait pu utiliser une SEQUENCE ou un compteur logiciel, voir la doc pour ça).
Et enfin on précise les champs de notre objet que l'on va vouloir persister (ps: si le nom des colonnes est identique au nom des attributs, il n 'y rien à faire), il faudra préciser le type d'attribut (string, integer, date, ...). En bleu ici le fait que la clé soit unique.



<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping
   PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.homeautomate.parameter">
   <class name="Parameter" >
      <id name="id" type="int">
         <generator class="increment"/>
      </id>

     <property name="cle" type="string" unique-key="cle">
      <property name="valeur" type="string"/>
      <property name="commentaire" type="string"/>
      
   </class>
</hibernate-mapping>



Et c'est tout !!!
En démarrant notre Serveur Hibernate vérifie si la table existe (Ps je n'ai pas préciser le nom de la table puisqu'elle aura le même nom que ma classe), si elle existe, il vérifiera les évolutions pour mettre à jour le schéma, si elle n'existe pas, la table sera créée. Attention pour créer les tables, il faut être précis sur les types des colonnes (not null, ... ) et surtout ! (J'ai cherché, il existe de mots réservés sur les dialectes ex: value en Mysql est prohibé comme nom de colonne, ou encore sequence en ORACLE, donc méfiance avec le nom des attributs.


Surprise dans la log de démarrage du Serveur : 


INFO  DatabaseMetadata - table not found: Parameter
INFO  DatabaseMetadata - table not found: Parameter
DEBUG SchemaUpdate - create table Parameter (id integer not null, cle varchar(255), valeur varchar(255), commentaire varchar(255), primary key (id), unique (cle))
INFO  SchemaUpdate - schema update complete

Et si je regarde le résultat dans MySQLAdministrator : 




Utilisation en java : 


Exemple : je veux récupérer un paramètre : 


Après avoir fait un parameterManager, une Dao interface, et une DaoHibernateIlmpl (voir conception), on m'oblige à implémenter une méthode public Parameter getParameter(ParameterEnum key) qui retourne une interface Parameter et qui prends en paramètre une ParameterEnum de nom key.


package com.homeautomate.dao;

import com.homeautomate.parameter.Parameter;

import constant.ParameterEnum;

public class ParameterDaoHibernateImpl extends
AbstractDaoHibernateImpl<Parameter> implements IParameterDao {

public Parameter getParameter(ParameterEnum key) {
Parameter param = new Parameter();
param.setCle(key.name());
return findByQbeUnique(param);
}

}

Commentaires : 


    J'étends une AbstractDaoHibernateImpl qui me permettra de récupérer des méthodes sympa, m'affranchir de l'ouverture et fermeture des sessions. Vous noterez également au passage un peu de généricité JAVA5 (<Parameter>). J'utilise ici un findByQbe (Query by example). En fait ici on va rechercher dans la table paramètres tous les paramètres qui ont comme clé la valeur de key passée en paramètre.
J'ai suffixé par unique la Qbe parceque je considère que la clé est unique. Allez remontons d'un niveau, allons voir dans l'Abstract du dessus :


package com.homeautomate.dao;


import java.util.List;


import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Example;


public class AbstractDaoHibernateImpl<T> implements AbstractDao<T> {


private static Configuration cfg;
private static SessionFactory sessionFactory;


private void initializeConfiguration() {
if (cfg == null) {
cfg = new Configuration();
cfg.configure("com/homeautomate/hibernate.cfg.xml");
sessionFactory = cfg.buildSessionFactory();


}


}


public List<T> findByQbe(Object entity) {
initializeConfiguration();
Session session = sessionFactory.openSession();
try {
Criteria crit = session.createCriteria(entity.getClass());
crit.add(Example.create(entity));
return crit.list();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
session.close();
}


}


public T findByQbeUnique(Object entity) {
initializeConfiguration();
Session session = sessionFactory.openSession();
try {
Criteria crit = session.createCriteria(entity.getClass());
crit.add(Example.create(entity));
return (T) crit.uniqueResult();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
session.close();
}
}
}


En rose (dans la méthode findByQbe classique) j'utilise la méthode list sur le criteria Hibernate. alors que dans la méthode findByQbeUnique j'utilise (en orange) uniqueResult(), On oblige hibernate à ne retourner qu'une seule valeur (Si on en est sur). Vous noterez l'initializeConfiguration qui va lire le fichier de conf.


Il est possible de faire plein d'opération sur les criteria.


Vouala.
Simple non ? Toutes ces méthodes sont présentes dans l'interface AbstractDao qui seront à implémenter si on utilise un autre ORM (OJB) ou un truc plus brutal genre JDBC.


A noter qu'Hibernate implémente l'API Java JPA (Java Persistance API)
JPA



Aucun commentaire:

Enregistrer un commentaire