Blue Flower

Chercher

Généralité et pré-requis
L'annotation dans le monde Java est une technique qui permet de "marquer" certains éléments au sein d'un programme afin de leur ajouter une propriété particulière. Ces annotations peuvent ensuite être utilisées à la compilation ou à l'exécution pour automatiser certaines tâches.
Les annotations peuvent être divisées en deux catégories, les annotations de mapping logique (vous permettant de décrire le modèle objet, les associations de classe, etc....) et les annotations de mapping physique (décrivant le schéma physique, les tables, les colonnes, les index, etc...)
Pour utiliser les annotations avec Hibernate il faut un certain nombre de pré-requis :

Une version de Hibernate supérieure ou égale à Hibernate 3.2.0.GA
une version de JDK supérieure ou égale JDK 5.0
D'abord, on commence par paramètrer le classpath; on copie toutes les bibliothéques du noyau Hibernate3, toutes les bibliothéques requises pour les annotations comme, "hibernate-annotations.jar" et "lib/ejb3-persistence.jar" de la distribution Hibernate Annotations dans le classpath.
On crée un repertoire lib dans le repertoire de développement et on y copie les fichiers.jar suivants:

//Hibernate 3
          commons-logging.jar
          dom4j.jar
          javassist.jar
          antlr.jar
          commons-collections.jar
          cglib.jar
          hibernate3.jar
          asm-attrs.jar
          asm.jar
          c3p0-0.9.1.jar
          hibernate-entitymanager.jar
          iijdbc.jar
          j2ee.jar
          concurrent-1.3.2.jar
          javassist.jar
          jboss-archive-browsing.jar
          ehcache-1.2.3.jar
          log4j-1.2.11.jar                    
  //Hibernate Annotations
          hibernate-annotations.jar
          ejb3-persistence.jar
   

Il faut que dans le classpath on ait tous les pré-requis pour la compilation et l'exécution de l'application.
L'annotation avec Hibernate a pour but de se passer des fichiers "hbm.xml" qui permettent de configurer chaque classe que l'on veut rendre persistente. Les différents éléments qu'on peut annotés seront ilustrés dan différents exemples.Les annotations sont directement injectés dans les classes relatives aux classes persistentes.

Configuration

Si on utilise Hibernate en s'appuyant sur un fichier de configuration, Les packages, les classes annotées sont déclarés dans le fichier de configuration XML (généralement hibernate.cfg.xml) comme le montre l'exemple de fichier de configuration hibernat.cfg.xml suivant :

<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
        <hibernate-configuration>
          <session-factory>
            <mapping package="test.animals"/>
            <mapping class="test.Flight"/>
            <mapping class="test.Sky"/>
            <mapping class="test.Person"/>
            <mapping class="test.animals.Dog"/>
            <mapping resource="test/animals/orm.xml"/>
          </session-factory>
        </hibernate-configuration>
 

On voit bien que l'on indique le package (test.animals) concerné par les annotions ainsi les différentes classes(test.Flight, test.Person, test.animals.Dog ) et ressources.

Programmation

Si on n'utilise pas un fichier de configuration pour se servir de Hibernate mais de la programmation, on rend visible les packages et les classes concernés par les annotions en procédant comme suit:

  sessionFactory = new AnnotationConfiguration()
                    .addPackage("test.animals") //the fully qualified package name
                    .addAnnotatedClass(Flight.class)
                    .addAnnotatedClass(Sky.class)
                    .addAnnotatedClass(Person.class)
                    .addAnnotatedClass(Dog.class)
                    .addResource("test/animals/orm.xml")
                    .configure().buildSessionFactory();

EntityManager

On peut aussi utiliser Hibernate EntityManager qui a son propre mécanisme de configuration. Il n'y a pas d'autres différences dans la façon d'utiliser les APIs d'Hibernate, excepté ce changement de routine de démarrage ou le fichier de configuration. Vous pouvez utiliser votre méthode de configuration favorite pour d'autres propriétés (hibernate.properties, hibernate.cfg.xml, utilisation des APIs, etc). Vous pouvez même mélanger les classes persistantes annotées et des déclarations hbm.cfg.xml classiques avec la même SessionFactory. Vous ne pouvez cependant pas déclarer une classe plusieurs fois (soit avec les annotations, soit avec un fichier hbm.xml). Vous ne pouvez pas non plus mélanger des stratégies de configuration (hbm vs annotations) dans une hiérarchie d'entités mappées.
Pour faciliter la procédure de migration de fichiers d'extension ".hbm.xml" vers les annotations, le mécanisme de configuration détecte la duplication de mappings entre les annotations et les fichiers d'extension ".hbm.xml" . Les classes décrites dans les fichiers hbm se voient alors affecter une priorité plus grande que les classes annotées. Vous pouvez changer cette priorité avec la propriété hibernate.mapping.precedence. La valeur par défaut est : hbm, class ; la changer en : class, hbm donne alors la priorité aux classes annotées lorsqu'un conflit survient

précédent suivant

 

Hibernate

Interface org.hibernate.SessionFactory

org.hibernate.SessionFactory est une interface dont le rôle principal est la création d'instance de classes implémentant l'interface org.hibernate.Session. Dans une application on a une seule instance d'une classe implémentant org.hibernate.SessionFactory et par la suite les demandes(requêtes) des clients obtiennent des instances de classes implémentant org.hibernate.Session à partir de cette SessionFactory. L'état interne de SessionFactory est immuable i.e qu'une fois qu'il est créé c'est lui qui est pris en compte tout au long de l'applicatrion. Cet état interne inclut toutes les métadonnées de la Cartographie d'Objet/Relationnel . Cet objet immuable tout long de l'application est obtenu comme suit :

 SessionFactory sessionFactory = new org.hibernate.cfg.Configuration().configure().buildSessionFactory();

La classe "org.hibernate.Configuration" permet d'atteindre les fichiers de configuration et les fichier de mapping des classes à mapper (POJO). L'objet Configuration lit les fichiers <nom-classe>.hbm.xml pour mettre en place le cadre de persistance; L'initialisation SessionFactory est chargée en mémoire et prend en compte les fichiers de configuration dans l'ordre suivant :

  1. le contenu de hibernate.properties
  2. le contenu de hibernate.cfg.xml
  3. les contenus de tous les mapping mentionnés dans hibernate.cfg.xml  

Une fois que la fabrique de session (SessionFactory) est créée, une connexion est créée et par la méthode openSession() on ouvre une session:

Session session = sessionFactory.openSession();

Après avoir ouvert et travaillé avec une session, on termine par close() de type void; cette méthode ferme la Session en relâchant toutes les ressources à savoir les caches, le jeu de connexions,etc ...


Interface org.hibernate.Session

L'interface org.hibernate.Session a un rôle centrale car il permet, de créer la connection à la base de données, de lire ou de détruire toute opération relative aux instances des classes java mappées ; c'est cet interface qui va réellement s'occuper de la notion de persistance.
Chaque instance d'objet mappé peut être dans un des états suivants:

transient : jamais persitante et non associé à une quelconque Session
peristent : associée à une et une seule Session
datached : précédemment persistante , non associée à une quelconque Session

Le cycle de vie d'une session est borné par le début et la fin d'une transaction logique (une longue transaction logique peut être réalisée par plusieurs transactions du SGBD).
C'est seulement lorsqu'une Session est ouverte, ainsi qu'une transaction que les objets des classes persistantes peuvent profiter de cette persistance. En général la mise à jour de la base de données se fait de façon optimisée lors de la validation de la transaction logique par la méthode commit()

Méthodes pour gérer une Session
Hibernate prend en compte toutes les modifications effectuées sur les objets persistants, les traduit en ordres SQL qui peuvent être, soit des insert, update ou delete, exécutables par le SGBD. Ceci est faisable après l'éxecution de la méthode Session.flush() qui sert à synhroniser l'état de la base de données avec celui de la mémoire centrale.
Un certain nombres de méthodes de l'interface Session permettent de bien gérer les données au sein d'une session ouverte:

void org.hibernate.Session.flush()

Le flush1 consiste à exécuter les ordres SQL permettant de synchroniser l'état de la base de données avec celui de la mémoire centrale


void org.hibernate.Session.clear()
Pour vider le cache (gestion des ressources).
org.hibernate.Session.close ()
fin de la session
org.hibernate.Session.commit()
Par défaut un flush est effectué aux instants suivants :
avant l'évaluation d'une requête, lors du org.hibernate.Session.commit() de la Transaction lors d'un appel explicite à flush() (ouf !)

Remarque
La documentation précise qu'un flush doit être exécuté avant l'évaluation d'une requête
Voici deux possibilités pour éviter la saturation du cache de second niveau :

Désactiver le cache de second niveau :
  - hibernate.cache.use_second_level_cache false 
  - hibernate.jdbc.batch_size 20 taille du paquet JDBC.

ou bien en appelant successivement, éventuellement plusieurs fois dans une même transaction logique, les deux méthodes suivantes :
session.flush() ; // Effectue toutes les modification en mémoire centrale dans la BD
et session.clear(); // Détruit toutes les instances mappées ainsi que save update delete


Interface org.hibernate.Transaction

La transaction telle qu'elle est définie ici n'a rien à voir avec les transactions sous-djacentes au SGBD sur lequel s'appuie Hibernate . C'est pourquoi on parle de transaction logique; on la définit au sein d'une session Hibernate et elle est instanciée par la méthode "Session.beginTransaction()" qui returne une instance Transaction et peut recouvrir plusieurs transaction du SGBD.
La création des transactions est liée à la propriété "hibernate.transaction.factory_class" qui indique la fabrique (factory) à utiliser. Par défaut cette fabrique est JDBCTransactionFactory qui fournit des JDBCTransaction. Ces deux classes se trouvent dans le paquetage "org.hibernate.transaction".
Si Hibernate fonctionne au dessus de JDBC, alors il nécessite que le auto commit soit à faux. La durée de vie d'une session correspond à exactement une transaction. Une idée est que pendant une transaction (et donc sa session) aucun dialogue interactif avec l'utilisateur ne doit avoir lieu. Si ce n'est pas le cas, la durée de la transaction risque d'être très longue et de dégrader les performances transactionnelles, par exemple si un verrou est posé en début de transaction sur une table pendant une heure parce que l'utilisateur a dû discuter avec ses collaborateurs pour prendre une décision.

Méthodes pour gérer une Transaction
La métode commit() de JDBCTransaction( returne un void) fait le flush de la Session associée ê la transaction et la transaction du SGBD est validée si elle a été démarrée par cette transaction logique.
De même la méthode de JDBCTransaction rollback() ( returne un void) force la transaction correspondante du SGBD à faire un rollback.

précédent suivant

Principe de la configuration

L'ORM Hibernate fonctionne dans différents environnements (Windows, Unix, Linux, etc ...) et avec différentes bases de données (Oracle, Mysql, PostGresql, etc ...); cela nécessite donc beaucoup de paramètres de configuration et la plupart d'entre eux ont des valeurs par défaut; chaque distribution d'hibernate contient dans son répertoire "etc/", un exemple de fichier hibernate.properties avec ces paramètres et leurs différentes valeurs. Si on utilise ce fichier "hibernate.properties", on le place dans le classpath du projet et on l'adapte en fonction des paramètres appropriées.
La Configuration de Hibernate se fait soit :

  1. - par programmation
  2. - via un fichier de configuration XML (hibernate.cfg.xml)
  3. - par Intégration de Hibernate à un serveur d'application J2EE

Dans tous les cas, une instance de la classe Configuration précise pour une application donnée, les propriétés (properties) à utiliser lorsqu'on crée une SessionFactory et par défaut, une instance de org.hibernate.cfg.Configuration utilise le fichier hibernate.properties.


Configuration par programmation

Principe

Une instance de la classe  "org.hibernate.cfg.Configuration" représente un ensemble de mappages des classes Java d'une application vers une base de données SQL. Ces  mappages sont constitués d'un ensemble de fichiers XML. Cette instance de la classe "Configuration" est utilisée pour construire un objet immuable, instance de la classe SessionFactory. On obtient une instance de la classe Configuration en l'instanciant directement et en spécifiant la liste des fichiers XML de mappage; si ces fichiers de mappage sont dans le classpath, on fait l'instanciation à l'aide de la méthode addResource() en procédant comme suit :

Configuration cfg = new Configuration()
.addClass("Item.hbm.xml")
.addClass("Bid.hbm.xml");

Les fichiers de mappages ici sont "Item.hbm.xml" et "Bid.hbm.xml".
Une alternative (parfois meilleure) est de construire les classes mappées et de laisser Hibernate trouver les fichiers XML  de mappage comme suit :

Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class)
.addClass(org.hibernate.auction.Bid.class);

En procédant comme ci-dessus, Hibernate va rechercher les fichiers de mappages  /org/hibernate/auction/Item.hbm.xml et /org/hibernate/auction/Bid.hbm.xml dans le classpath. Cette approche élimine les noms de fichiers en dur dans l' application.
La Configuration permet également de préciser des propriétés de configuration:

Configuration cfg = new Configuration()
.addClass(org.hibernate.auction.Item.class) .addClass(org.hibernate.auction.Bid.class) .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect") .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") .setProperty("hibernate.order_updates", "true");

Ce n'est pas le seul moyen de passer des propriétés de configuration à Hibernate. Les différentes options sont :

  1. passer une instance de java.util.Properties à Configuration.setProperties().
  2. Placer hibernate.properties dans un répertoire racine du classpath
  3. Positionner les propriétés System en utilisant java -Dproperty=value.
  4. Inclure des éléments <property> dans le fichier hibernate.cfg.xml (voir plus loin).

L'utilisation d'hibernate.properties est l'approche la plus simple si vous voulez démarrer rapidement; L'objet Configuration est un objet de démarrage qui sera supprimé une fois qu'une SessionFactory aura été créée.....


Configuration par fichier XML

Principe

Une des approches alternatives à celle citée ci-dessus est de spécifier toute la configuration dans un fichier nommé hibernate.cfg.xml. Ce fichier peut être utilisé à la place du fichier hibernate.properties, voire même peut servir à surcharger les propriétés si les deux fichiers sont présents. Ce fichier hibernate.cfg.xml de configuration XML doit par défaut se placer à la racine du CLASSPATH. Voici un exemple de fichier hibernate.cfg.xml :

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    
    <session-factory
        name="java:hibernate/SessionFactory">
        
        <property name="connection.datasource" >java:/comp/env/jdbc/MyDB</property >
        <property name="dialect" > org.hibernate.dialect.MySQLDialect</property >
        <property name="show_sql" > false</property >
        <property name="transaction.factory_class" >
            org.hibernate.transaction.JTATransactionFactory
        </property >
        <property name="jta.UserTransaction" >java:comp/UserTransaction</property >
        
        <mapping resource="org/hibernate/auction/Item.hbm.xml"/>
        <mapping resource="org/hibernate/auction/Bid.hbm.xml"/>
        
        <class-cache class="org.hibernate.auction.Item" usage="read-write"/>
        <class-cache class="org.hibernate.auction.Bid" usage="read-only"/ >
        <collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/>
    </session-factory >
</hibernate-configuration >

L'avantage de cette approche est l'externalisation des noms des fichiers de mapping de la configuration. Le fichier hibernate.cfg.xml est également plus pratique quand on commence à régler le cache d'Hibernate.
Avec la configuration par fichier XML, démarrer Hibernate devient simple - voir comment fonctionne la déclaration de la variable sessionFactory -

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();

Configuration par intégration de Hibernate à un serveur d'application J2EE

Principe

Hibernate possède les points suivants d'intégration à l'infrastructure 2EE:

  1. Source de données gérée par le conteneur :
    .....
  2. Association JNDI automatique :
    .....
  3. Association de la Session à JTA :
    ....
  4. Déploiement JMX :
    .....

La Classe Configuration

La classe "org.hibernate.cfg.Configuration" permet d'atteindre les propriétés de configuration d'une application java utilisant Hibernate et les fichiers de mapping des classes mappées; ces propriétés de configuration et les mapping servent à créer une SessionFactory (Fabrique de Session). Toute application java utilisant Hibernate a normalement besoin d'une seule configuration, donc d'une seule instance de SessionFactory et toutes les sessions de l'application seront ouvertes via l'interface Session et s’appuieront sur l'instance unique de SessionFactory. Hibernate permet à une application d'instancier plus d'une SessionFactory ce qui est pratique lorsqu'on utilise plus d'une base de données .
Habituellement, vous voulez que la SessionFactory crée les connexions JDBC et les mette dans un pool pour vous. Si vous suivez cette approche, ouvrir une Session est aussi simple que :

Session session = sessionFactory.openSession();

précédent suivant

1. Généralités

Les requêtes HQL et SQL Natives sont représentées par une instance d'une classe qui implémente l'interface "org.hibernate.Query". Cette interface offre des méthodes pour la liaison des paramètres, la gestion des ensembles de résultats et l'exécution des requêtes réelles. On obtient une instance d'une classe implémentant Query en utilisant la Session courante comme suit:

Query query= session.createQuery(hql);

 

Exemple d'utilisation:

String hql = "FROM com.hibernatebook.criteria.Employee";
Query query = session.createQuery(hql);
List results = query.list();

La requête "hql" peut être :

  • Une clause FROM  comme ci-dessus.
  • Une clause AS, ...
  • Une clause SELECT..
  • Une clause WHERE..
  • Une clause ORDER BY..
  • Une clause GROUP BY......

......

1.1. Interface org.hibernate.Query

C'est la représentation objet d'une requête Hibernate. L'ordre peut comporter des paramètres nommés, (par exemple :nom). Un même paramètre peut apparaître plusieurs fois dans la requête. On peut aussi utiliser le "?" pour un paramètre comme en JDBC, tout en sachant qu' ils sont numérotés à partir de 0 contrairement à JDBC. Toutefois on ne peut pas mélanger les deux notations de paramètre (celle de JBC et celle de Hibernate). La durée de vie d'une requête est limitée à celle de la Session qui l'a créée. les  principales méthodes de l'interface "org.hibernate.Query"  sont:

  1. - executeUpdate() exécute l'instruction update ou delete
  2. - List list() renvoie le résultat d'une requête comme une liste, si plusieurs entités par ligne l'élément de liste est un Object[]
  3. - Object uniqueResult() pour récupérer l'unique résultat d'une requête ; null si aucun résultat et exception NonUniqueResultException si plus d'un résultat.
  4. - Query setInteger (int position, int val)
  5. - Query setInteger (String name, int val)
  6. - Query setString (int position, String val)
  7. - Query setString (String name, String val)

1.2. Interface org.hibernate.SQLQuery

L'interface SQLQuery dérive de l'interface Query; on se sert de SQLQuery pour construire des requêtes sql. L'execution de requêtes natives est contrôlée par l'interface SQLQuery; ccelui est obtenu e nfaisant appel à à la méthode Session.createSQLQuery(). The following sections describe how to use this API for querying.

.....


II. Le langage HQL

1. Généralités

HQL (Hibernate Query Langage) est un langage d'interrogation des classes persistantes avec une syntaxe proche du SQL. Cependant les objets manipulés sont les classes et membres du mapping ce qui n'est pas le cas pour SQL qui réalise les requêtes directement sur la base de données. HQL génére la requête sql et l'exécute sur la BD concernée par l'application de faîon transparente . HQL contient des fonctionnalités spécifiques au modèle objet.
HQL supporte le Polymorphisme, le mapping des Associations, et il est beaucoup moins verbeux que le SQL. Pour interroger les classe persistantes ( envoyer des requêtes sql) en plus de SQL Natives ou HQL , on se sert de l' API Criteria (voir QBC ( Query By Criteria) et QBE( Query By Example)) .

Pourquoi utiliser HQL ?

  1. - HQL prend en compte toutes les opérations relatives aux requêtes sql classiques.
  2. - Les requêtes HQL returnent des objets et ainsi on dispose d'une facilité d'utilisation. Cela élimine le faite qu'on soit obligé de créer l'objet qu'on veut utiliser et de remplir un à un les champs correspondant aux données à utiliser.
  3. - HQL supporte aussi les requêtes appliquées sur des objets se trouvant dans un environnement où on a des classes obéissant au polymorphisme (Polymorphic Queries).
  4. - Supporte des propriétész avancées de sql comme la pagination, "fetch join" avec des profile dynamiques, Inner/outer/full joins et le produit cartésien .
  5. - Hibernate supporte aussi les fonctions d'aggragation comme (max, avg), le regroupement (GROUP BY), ORDER BY, les sous-requêtes et les appels de fonctions (voir procédurs stockées).

2- Syntaxe du langage HQL

Les requêtes HQL sont de trois sortes:

  • Clauses
  • Fonctions d'aggrégations
  • Sous-requêts

2.1. Clauses :
2.1.2. FROM :
C'est la clause la plus simple utilisée par HQL; elle spécifie l'objet qui sera retourné comme résultat de la requête; cette clause est utilisée presque toujours avec la clause select:
syntaxe :
from object [as object_alias]* object_alias (<-- une alias ). from comp.Dept as dept
Cette requête retournera toutes les instances de l'objet dept .
2.1.3.  select :
Elle spécfie des objets et des propriétés qui seront retournés comme résultat de la reqête; en général elle s'utilise avec la clause from .
syntaxe :
select [object.]property

select dept.mgr from comp.Dept as dept

Retournera toutes les valeurs de mrg de toutes les instances de l'objet dept.

2.1.4. where :
Précise une ou des conditions à satisfaire pour les résultat retournés.Cette clause s'utilise en général avec les clauses select et/ou from.
syntaxe :
where condition
Ici les ou la condition est une combinaison des opérateurs logiques =, >, AND, NOT etc.

select dept.mgr from comp.Dept as dept where dept.emp_no > 10
Will return all instances of mgr in dept whose corresponding emp_no values are greater than 10.

2.1.5. order by :
La liste retounée par la requête peut être triée par n'importe quelle propriété de la classe ou du composant retourné; cett clause est utiliséé avec les clauses select et from .
syntaxe :
order by object0.property0 [asc|desc][, object1.property0]...
Par défaut l'ordre est ascendant .

select dept.mgr from comp.Dept as dept order by dept.emp_no asc
Cette requête retourne une liste de toutes les valeurs de dept.mgr des instances de l'objet dept ordonnées suivant les valeurs ascendantes de dept.emp_no

2.1.6. group by :
Si la requête retourne des valeurs aggrégées, celles ci peuvent être groupées par propriété ou composant.
syntaxe :
group by object0.property0[, object1.property0]...

select dept.emp_no from comp.Dept as dept group by dept.mgr
Cette requête retourne une liste de toutes les valeurs des propriétés dept.emp_no des instances de dept en les regroupant suivant les valeurs des dept.mgr

2.2. Fonctions d'aggrégations
Les requêtes HQL peuvent aussi retourner le résultat de fonctions d'aggrégation (comme sum, average, et count) sur les propriétés de tous les objets satisfaisant d'autres critéres dans la requête; Voici une liste des fonctions d'aggrégation avec leur syntaxe:

  1. - count( [ distinct | all ] object | object.property )
  2. - count(*) (équivalent à count(all ...), compte les valeurs nulles aussi)
  3. - sum ( [ distinct | all ] object.property)
  4. - avg( [ distinct | all ] object.property)
  5. - max( [ distinct | all ] object.property)
  6. - min( [ distinct | all ] object.property)

2.3. Sous-requêtes
Pour les bases de données supportant les sous-requêtes, Hibernate supporte les sous-requêtes dans les requêtes. Une sous-requête doit être entre parenthèses (souvent pour un appel à une fonction d'agrégation SQL). Même les sous requêtes corrélées (celles qui font référence à un alias de la requête principale) sont supportées.
Notez que les sous-requêtes HQL peuvent arriver seulememnt dans les clauses select ou where.

3. Quelques exemples

3.1 Exemple d'utilisation:

Query query = session.createQuery("insert into Stock(stock_code, stock_name)" +
    			"select stock_code, stock_name from backup_stock");
int result = query.executeUpdate();

La méthode  query.executeUpdate() retournera le nombre d'enregistrement enserré, mis à jour ou détruits .

.........

 


Interface org.hibernate.Criteria

Criteria est une interface dérivant de CriteriaSpecification. Criteria est un API simplifié permettant de recouvrir à partir de BD, des données contenant ou composées d'objets en se basant sur des critères; cette fonctionnalité convient pour une recherche avec plusieurs conditions. Une instance Session avec sa méthode createCriteria() permet de créer une ou des instance de Criteria ; Session est en l’occurrence une Fabrique de Critria.
Cette méthode a comme paramètre le nom de la classe mappée sur laquelle on va lancer la requête et en voici une exemple:
Criteria criteria= session.createCriteria(Order.class);
Cette instruction retournent une instance de la classe Criteria pour la classe mappée Order.
Chaque classe de l'API Criteria représente un aspect de l'approche relationel et l' API Criteria a cinq parties qui sont souvent utilisée :

  1. Criteria
  2. Criterion
  3. Restrictions
  4. Projection
  5. Order

Les critères de recherche utilisables dans des requêtes sql ont une représentation orientée objet en l'interface Criterion. En d'autres termes, l'interface Criterion est la représentation orientée objet de la clause "where" dans une requête sql classique et La classe "org.hibernate.criterion.Restrictions" définit des méthodes de type de Criterion (critère de recherche) pré-définis. (voir interfaces Criterion et Restrictions).
Exemple :

Criteria criteria= session.createCriteria(Order.class);
Criterion crit=Restriction.eq("orderId","OD00009");
criteria.add(crit);

Cet exemple introduit avec Criterion la clause "where" qui, losqu'elle est ajoutée à l'objet Criteria , offre une requête complète avec des restrictions ; ceci est fait à travers la méthode "org.hibernate.criterion.Restrictions.eq()";

la classe org.hibernate.criterion.Restrictions :
La classe Restrictions est essentiellement une fabrique d'instances de classe Criterion dont toutes les méthodes sont "static". Cette classe Restrictions fournit presque toutes les méthodes permettant d"implémenter des restrictions que l'on peut introduire via la clause "where" d'une requête sql classique; ces méthodes sont:
(eq()), (and()),(like()) etc...

la classe org.hibernate.criterion.Projection :
La classe Projection est une représentation orientée objet de l'ensemble des résulats d'une projection au sens d'une requête sql; une projection, au sens d'une requête sql classique, est une instruction sql permettant de sélectionner un ensemble de colonnes dans une table; ce sont donc les champs qui se trouvent après la clause select d'une requête sql. La classe Projection permet de faire une projection au sens d'une requête sql; pour cela, on se sert de la méthode addProjection() de la classe ProjectionList. La méthode addProjection() de la classe Creiteria retourne un objet Criterion comme le montre l'exemple suivant:

List orders = session.createCriteria(Order.class)
     .setProjection( Projections.projectionList()
      .add( Projections.rowCount())).list();

Ci-dessus on illustre quelques utilisations de la projection :
Prenons la requête sql simple suivante:
SELECT * FROM ORDER
Cette requête est une projection qui liste tous les éléments de la table ORDER; on peut facilement se servir des classes de l' API Criteria pour réaliser cette requête:
List orders= session.createCriteria(Order.class).list();
Et pour des raisons de simplicité et de clarté on peut décomposer cette instruction en deux instructions très simples comme suit:

Criteria criteria= session.createCriteria(Order.class);  
List orders=criteria.list();

Ensemble les deux instructions ci-dessus permettent de retirer de la BD les èlèments permettant de remplir les champs de l'objet mappè Order, les rangent dans une liste et retourne la liste sous forme d'objet.
Si on avait à retirer un élément, on utiliserait la reqête sql
SELECT NAME FROM PRODUCT
et on procéderait avec l'approche objet comme suit :

List products=session.createCriteria(Product.class)
     . setProjection(Projection.property(\"name\"))
     .list();

Il est claire que le champ présent dans la clause clause SELECT sert de paramètre à la méthode Projection.property(); le reste de l'instruction est la construction de la liste que l'on va avoir en sortie en effet session.createCriteria a comme paramètre la classe mappée sur laquelle on va travailler, setProjection recupére toute les instances contenant "name" et list() construit la liste de tous les résultats.
Si on a plus d'une colonne dans la clause SELECT
SELECT NAME, ID FROM PRODUCT
on se sert de la methode add() pour ajouter chaque champ conserné et cela donne avec QBC :

List products =session.createCriteria(Product.class).setProjection(
    Projections.propertyList()
        .add(Projection.property(\"name\"))
        .add(Projection.property(\"id\"))
    )
    .list();

Si on réalise des requêtes complexes contenant des Joins comme suit:
SELECT O.*, P.* FROM ORDERS O, PRODUCT P WHERE O.ORDER_ID=P.ORDER_ID;
Cela se traduit facilement avec QBC :

List orders = session.createCriteria(Order.class)
            .setFetchMode("products",FetchMode.JOIN)
            .list();

C'est aussi simple que ça en se servant de la méthode setFetchMode() de Criteria. Exemples de Projection suivie de restrictions
Pour faire une projection d'un certain nombre de colonnes de la BD avec un cetain nombre de condition on se sert de la classe Restriction; toute les condition de retriction utilisables danss une requête sql sont utilisables aussi dans QBC et les plus souvent utilisée sont:

Restriction.between cettte méthode utilisée pour introduire une contrainte comme dans sql
Restriction.eq cettte méthode utilisée pour introduire une contrainte comme dans sql.
Restriction.ge cettte méthode utilisée pour introduire une contrainte comme dans sql.
Restriction.gt cettte méthode utilisée pour introduire une contrainte comme dans sql.
Restriction.idEq is used to apply an "equal" constraint to the identifier property.
Restriction.in is used to apply an "in" constraint to the field.
Restriction.isNotNull is used to apply an "is not null" constraint to the field.  
Restriction.isNull is used to apply an "is null" constraint to the field.
Restriction.ne is used to apply a "not equal" constraint to the field.   

Donc une requête sql comme :
SELECT * FROM ORDERS WHERE ORDER_ID="1092";
se traduit comme

   List orders= session.createCriteria(Order.class)
               .add(Restrictions.eq("orderId","1092"))
               .list();

Appliquez des contrainte est aussi simple que ça; on peut l'illuustrer encore par l'exemple ci-dessus:

   SELECT O.*, P.* FROM ORDERS O, PRODUCT P WHERE
    O.ORDER_ID=P.ORDER_ID AND P.ID="1111";

que l'on traduit en utilisant QBC comme suit:

List orders = session.createCriteria(Order.class)
        .setFetchMode("products",FetchMode.JOIN)
        .add(Restrictions.eq("id","1111"))
        .list();
 

En ajoutant la restriction en se servant de lamethode setFetchMode() on obtient la même chose que ci-dessus;

List orders = session.createCriteria(Order.class)
     .setProjection( Projections.projectionList()
     .add( Projections.count("id") ))
     .list();
 

De la même manière les fonction d'aggrégation sont utilisées comme Projections.count() dans l'exemple ci-dessus; chaque fonction d'aggrégation utilisée a comme argument le nom de la colonne concernée ( ici c'est 'id' pour la fonction count) et le résultat est donné en l eregroupant selon certaines conditions relative è cerataines colonnes; pour cela on se sert de la méthode Projections.groupProperty() ; comme exemple considérons la requête suivant:

SELECT COUNT(ID) FROM ORDER HAVING PRICETOTAL>2000 GROUP BY ID
On a la même chose si on utilise QBC comme suit:

List orders = session.createCriteria(Order.class)
     .setProjection( Projections.projectionList()
      .add( Projections.count("id") )
       .add( Projections.groupProperty("id") )
     )
      .list(); 
 

la classe org.hibernate.criterion.Order :
LA classe Order représente la clause "order by de sql. Dans la suite on va voir comment on se sert de ces classes pour se passer des Sql Natif ou HSQL dans les cas où c'est possible .
QBC (Query By Criteria)
C'est vraiment avec QBC que Hibernate s'utilise en se passant complétement des requêtes Sql; ces dernières sont gérées de faîon transparente par l' API; le développeur se sert uniquement d'objets comme l'ilustre l'exemple ci-dessus:
Exemple :

 Criteria criteria = session.createCriteria(Order.class);
 criteria.add( Expression.eq("id", "I009") );
 List result = criteria.list();

On voit qu'on obtient un instance de la classe Criteria par la méthode session..createCriteria qui a comme paramètre le nom de la classe mappée au lieu d' une requête et puis les critères sont ajoutée via la méthode criteria.add() qui prend comme paramètre ce que retourne la méthode Expression.eq().

QBE (Query By Example)
Cette approche n'est très différente de l'utilisation de QBC; en se servant de QBC on sert de la partie rerstriction de la clause where tandisque avec QBE de la partie exemple de la resrtction; cette partie exemle est introduite par par " as like 'I%'" sachant qu' ici on s'interesse aux valeurs commenîant par 'I'. L'idée principale de QBE est que l'application offre une instance de la calsse sur laquelle on lance la requête avec la valeur de la propriété set pour toute valeur par default. .
Example :

   User exampleUser = new User();
   exampleUser.setFirstname("Max");
  Criteria criteria = session.createCriteria(User.class);
  criteria.add( Example.create(exampleUser) );
  List result = criteria.list();

Cette instruction retourne tous les objets persistants contenant le champ Firstname qui a comme valeur "Max".

précédent suivant

Hibernate

Architecture

Architecture Hibernate
figure 1.0

L'architecture de Hibernate est faite de telle sorte que l'on puisse l'intégrer facilement à d'autre APIs sans qu'on soit obligé de le connaître en détail.
Hibernate est composé de plusieurs classes et interfaces et les développeurs ne se servent que d'une petite partie.
Lorsqu'on travaille avec l'ORM Hibernate, le chargement d'un objet mappé depuis une base de données ou vers une base de données n'est possible que si on a une session ouverte et active.
L'objet Session offre l'interface principale permettant de travailler avec des bases de données. Ceci revient à dire que des objets persistants sont sauvegardés dans la base de données ou retirés de cette dernière en se servant d'un objet Session.
Il est très facile de créer un objet Session ; outre le faite qu'il permet de faire la connexion à la base de données, en s'en servant, on a automatiquement le maintien d'une cache durant la durée de vie du thread au sein duquel elle est ouverte par l'application (une requête).
Typiquement, une session est créée pour gérer une seule "requête" au sein d'une application mais il arrive qu'on s'en sert pour gérer plusieurs requêtes;  dans tous les cas un objet Session ne doit pas rester ouvert longtemps.
Pour rendre une session active, il faut tout simplement la déclarer comme suit:
org.hibernate.Session session = sessionFactory.openSession ();

Pour modifier la BD, on se sert d'un objet Transaction via un objet Session; Toute transaction Hibernate d'avec JDBC, JTA ou CORBA est gérée par un TransactionManager. Comme l'objet Session, un objet Transaction ne doit pas rester ouvert trop longtemps si c'est possible.
Pour faire des requêtes sur la BD avec Hibernate, on se sert des objets Query et Criteria.

Composants

Les principaux composants permettant d'obtenir une session Hibernate sont:

  • SessionFactory (org.hibernate.SessionFactory)
    Cette classe doit être créée assez tôt dans l'application car c'est elle qui constitue un cache threadsafe (immuable) des mappings vers une (et une seule) base de données. C'est une fabrique (factory) de Session et c'est lui le client de ConnectionProvider (interface utilisée par le système) pour obtenir une connection à la BD utilisée. Un objet SessionFactory peut contenir un cache optionnel de données (de second niveau) qui est réutilisable entre les différentes transactions que cela soit au sein du même processus (JVLM) ou par plusieurs noeuds d'un cluster.
  • Session (org.hibernate.Session)
    L'objet Session est, mono-threadé, à durée de vie courte; il représente une conversation entre l'application et l'entrepôt de persistance. Il encapsule une connexion JDBC; C'est une fabrique (Factory) des objets Transaction; il contient un cache (de premier niveau) des objets persistants; ce cache est obligatoire et est utilisé lors de la navigation dans le graphe d'objets ou lors de la récupération d'objets par leur identifiant.
  • Transaction (org.hibernate.Transaction)
    L'utilisation de cette classe est Optionnelle; L'objet de type Transaction est mono-threadé, à vie courte et est utilisé par l'application pour définir une unité de travail atomique. Une Session peut fournir plusieurs Transactions dans certains cas. Toutefois, la délimitation des transactions, via l'API d'Hibernate ou par la Transaction sous-jacente, n'est jamais optionnelle!
  • ConnectionProvider (org.hibernate.connection.ConnectionProvider)
    L'utilisation de cette fabrique de (pool de) connexions JDBC est Optionnelle. Cet objet n'est pas exposé à l'application, mais peut être étendu/implémenté par le développeur.
  • TransactionFactory (org.hibernate.TransactionFactory)
    Son utilisation est Optionnelle.C'est une fabrique d'instances de Transaction non exposée à l'application, mais qui peut être étendue/implémentée par le développeur.

Les trois étapes suivantes permettent de démarrer une application Hibernate:
On commence par une Instanciation de l'objet Configuration ....,
puis on réalise une Construction de la sessionFactory......
et on fait une Demande de la nouvelle session à la SessionFactory ....
(Tout ceci sera illustré par des exemples)........


Les classes métiers persistantes

Les classes persistantes d'une application sont les classes qui implémentent les entités d'un problème métier (ex. Client et Commande dans une application de commerce électronique). Toutes les instances d'une classe persistante ne sont pas forcément dans l'état persistant -  une instance peut être éphémère (NdT : transient) ou détachée.
Hibernate fonctionne de manière optimale lorsque ces classes suivent quelques règles simples, aussi connues comme le modèle de programmation Plain Old Java Object (POJO). Cependant, aucune de ces règles ne sont des besoins absolus. En effet, Hibernate3 suppose très peu de choses à propos de la nature de vos objets persistants. Vous pouvez exprimer un modèle de domaine par d'autres moyens : utiliser des arbres d'instances de Map, par exemple.

Implémentation de equals() et de  hashCode()

On doit surcharger les méthodes equals() et hashCode() si on a l'intention de mettre des instances de classes persistantes dans un Set (ceci est la manière recommandée pour représenter des associations pluri-valuées) et ou d'utiliser le ré-attachement d'instances détachées.
Hibernate garantit l'équivalence de l'identité de la classe persistante (ligne dans une  base de données) et l'identité de la classe Java seulement à l'intérieur de la portée d'une session particulière. Donc dès que nous mélangeons des instances venant de différentes sessions, nous devons implémenter equals() et hashCode() si nous souhaitons avoir une sémantique correcte pour les Sets.

La manière la plus évidente est d'implémenter equals()/hashCode() en comparant la valeur de l'identifiant des deux objets. Si cette valeur est identique, les deux doivent représenter la même ligne dans la base de  de données; ,dans ce cas ils sont donc égaux (si les deux sont ajoutés à un Set, nous n'aurons qu'un seul élément dans le Set). Malheureusement, nous ne pouvons pas utiliser cette approche avec des identifiants générés ! Hibernate n'assignera de valeur d'identifiant qu'aux objets qui sont persistants, une instance nouvellement créée n'aura donc pas de valeur d'identifiant ! De plus, si une instance est non sauvegardée et actuellement dans un Set, le sauvegarder assignera une valeur d'identifiant à l'objet. Si equals() et hashCode() sont basées sur la valeur de l'identifiant, le code de hachage devrait changer, rompant le contrat du Set.  Notez que ceci n'est pas un problème d'Hibernate, mais la sémantique normale de Java pour l'identité d'un objet et l'égalité. On implémente equals() et hashCode() en utilisant l'égalité par clé métier. L'égalité par clé métier signifie que la méthode equals() compare uniquement les propriétés qui forment une clé métier, une clé qui identifierait notre instance dans le monde réel (une clé candidate naturelle).

 

(à suivre)..

précédent