|
|
|
|
|
|
Développons en Java v 1.60 | |
| Copyright (C) 1999-2011 Jean-Michel DOUDOUX |
![]() |
![]() |
![]() |
Hibernate est une solution open source de type ORM (Object Relational Mapping) qui permet de faciliter le développement de la couche persistance d'une application. Hibernate permet donc de représenter une base de données en objets Java et vice et versa.
Hibernate facilite la persistence et la recherche de données dans une base de données en réalisant lui même la création des objets et les traitements de remplissage de ceux-ci en accédant à la base de données. La quantité de code ainsi épargnée est très importante d'autant que ce code est généralement fastidieux et redondant.
Hibernate est très populaire notamment à cause de ses bonnes performances et de son ouverture à de nombreuses bases de données.
Les bases de données supportées sont les principales du marché : DB2, Oracle, MySQL, PostgreSQL, Sybase, SQL Server, Sap DB, Interbase, ...
Le site officiel http://www.hibernate.org contient beaucoup d'informations sur l'outil et propose de le télécharger ainsi que sa documentation.
La version utilisée dans cette section est la 2.1.2 : il faut donc télécharger le fichier hibernate-2.1.2.zip et le décompresser dans un répertoire du système.
Ce chapitre va utiliser Hibernate avec une base de données de type MySQL possédant une table nommée "personnes".

Hibernate a besoin de plusieurs éléments pour fonctionner :
Une fois ces éléments correctement définis, il est possible d'utiliser Hibernate dans le code des traitements à réaliser. L'architecture d'Hibernate est donc la suivante :

Ce chapitre survole uniquement les principales fonctionnalités d'Hibernate qui est un outil vraiment complet : pour de plus amples informations, il est nécessaire de consulter la documentation officielle fournie avec l'outil ou consultable sur le site web.
Ce chapitre contient plusieurs sections :
Cette classe doit respecter le standard des javabeans notamment encapsuler les propriétés dans ses champs private avec des getters et setters et avoir un constructeur par défaut.
Les types utilisables pour les propriétés sont : les types primitifs, les classes String et Dates, les wrappers, et n'importe quelle classe qui encapsule une autre table ou une partie de la table.
| Exemple : |
import java.util.Date;
public class Personnes {
private Integer idPersonne;
private String nomPersonne;
private String prenomPersonne;
private Date datenaissPersonne;
public Personnes(String nomPersonne, String prenomPersonne, Date datenaissPersonne) {
this.nomPersonne = nomPersonne;
this.prenomPersonne = prenomPersonne;
this.datenaissPersonne = datenaissPersonne;
}
public Personnes() {
}
public Date getDatenaissPersonne() {
return datenaissPersonne;
}
public Integer getIdPersonne() {
return idPersonne;
}
public String getNomPersonne() {
return nomPersonne;
}
public String getPrenomPersonne() {
return prenomPersonne;
}
public void setDatenaissPersonne(Date date) {
datenaissPersonne = date;
}
public void setIdPersonne(Integer integer) {
idPersonne = integer;
}
public void setNomPersonne(String string) {
nomPersonne = string;
}
public void setPrenomPersonne(String string) {
prenomPersonne = string;
}
} |
Pour assurer le mapping, Hibernate a besoin d'un fichier de correspondance (mapping file) au format XML qui va contenir des informations sur la correspondance entre la classe définie et la table de la base de données.
Même si cela est possible, il n'est pas recommandé de définir un fichier de mapping pour plusieurs classes. Le plus simple est de définir un fichier de mapping par classe, nommé du nom de la classe suivi par ".hbm.xml". Ce fichier doit être situé dans le même répertoire que la classe correspondante ou dans la même archive pour les applications packagées.
Différents éléments sont précisés dans ce document XML :
Le fichier débute par un prologue et une définition de la DTD utilisée par le fichier XML.
| Exemple : |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> |
Le tag racine du document XML est le tag <hibernate-mapping>. Ce tag peut contenir un ou plusieurs tag <class> : il est cependant préférable de n'utiliser qu'un seul tag <class> et de définir autant de fichiers de correspondance que de classes.
Exemple :
| Exemple : |
<?xml version="1.0"?><!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"><hibernate-mapping>
<class name="Personnes" table="personnes">
<id name="idPersonne" type="int" column="idpersonne">
<generator class="native"/>
</id>
<property name="nomPersonne" type="string" not-null="true" />
<property name="prenomPersonne" type="string" not-null="true" />
<property name="datenaissPersonne" type="date">
<meta attribute="field-description">date de naissance</meta>
</property>
</class>
</hibernate-mapping> |
Le tag <class> permet de préciser des informations sur la classe qui va encapsuler les données.
Ce tag possède plusieurs attributs dont les principaux sont:
Nom |
Obligatoire |
Rôle |
name |
oui |
nom pleinement qualifié de la classe |
table |
oui |
nom de la table dans la base de données |
dynamic-update |
non |
booléen qui indique de ne mettre à jour que les champs dont la valeur a été modifiée (false par défaut) |
dynamic-insert |
non |
booléen qui indique de ne générer un ordre insert uniquement pour les champs dont la valeur est non nulle (false par défaut) |
mutable |
non |
booléen qui indique si les occurrences peuvent être mises à jour (true par défaut) |
Le tag enfant <id> du tag <class> permet de fournir des informations sur l'identifiant d'une occurrence dans la table.
Ce tag possède plusieurs attributs :
Nom |
Obligatoire |
Rôle |
name |
non |
nom de la propriété dans la classe |
type |
non |
le type Hibernate |
column |
non |
le nom du champ dans la base de données (par défaut le nom de la propriété) |
unsaved-value |
non |
permet de préciser la valeur de l'identifiant pour une instance non encore enregistrée dans la base de données. Les valeurs possibles sont : any, none, null ou une valeur fournie. Null est la valeur par défaut. |
Le tag <generator>, fils obligatoire du tag <id>, permet de préciser quel est le mode de génération d'un nouvel identifiant.
Ce tag possède un attribut :
Attribut |
Obligatoire |
Rôle |
class |
oui |
précise la classe qui va assurer la génération de la valeur d'un nouvel identifiant. Il existe plusieurs classes fournies en standard par Hibernate qui possèdent un nom utilisable comme valeur de cet attribut. |
Les classes de génération fournies en standard par Hibernate possèdent chacun un nom :
Nom |
Rôle |
increment |
incrémentation d'une valeur dans la JVM |
identity |
utilisation d'un identifiant auto-incrémenté pour les bases de données qui le supportent (DB2, MySQL, SQL Server, ...) |
sequence |
utilisation d'une séquence pour les bases de données qui le supportent (Oracle, DB2, PostgreSQL, ...) |
hilo |
utilisation d'un algorithme qui utilise une valeur réservée pour une table d'une base de données (par exemple une table qui stocke la valeur du prochain identifiant pour chaque table) |
seqhilo |
idem mais avec un mécanisme proche d'une séquence |
uuid.hex |
utilisation d'un algorithme générant un identifiant de type UUID sur 32 caractères prenant en compte entre autre l'adresse IP de la machine et l'heure du système |
uuid.string |
idem générant un identifiant de type UUID sur 16 caractères |
native |
utilise la meilleure solution proposée par la base de données |
assigned |
la valeur est fournie par l'application |
foreign |
la valeur est fournie par un autre objet avec lequel la classe est associée |
Certains modes de génération nécessitent des paramètres : dans ce cas, il faut les définir en utilisant un tag fils <param> pour chaque paramètre.
Le tag <property>, fils du tag <class>, permet de fournir des informations sur une propriété et sa correspondance avec un champ dans la base de données.
Ce tag possède plusieurs attributs dont les principaux sont :
Nom |
Obligatoire |
Rôle |
name |
oui |
précise le nom de la propriété |
type |
non |
précise le type |
column |
non |
précise le nom du champ dans la base de données (par défaut le nom de la propriété) |
update |
non |
précise si le champ est mis à jour lors d'une opération SQL de type update (par défaut true) |
insert |
non |
précise si le champ est mis à jour lors d'une opération SQL de type insert (par défaut true) |
Le type doit être soit un type Hibernate (integer, string, date, timestamp, ...), soit les types primitif Java ou de certaines classes de base (int, java.lang.String, float, java.util.Date, ...), soit une classe qui encapsule des données à rendre persistantes.
Le fichier de correspondance peut aussi contenir une description des relations qui existent avec la table dans la base de données.
Pour exécuter Hibernate, il faut lui fournir un certain nombre de propriétés concernant sa configuration pour qu'il puisse se connecter à la base de données.
Ces propriétés peuvent être fournies sous plusieurs formes :
Les principales propriétés pour configurer la connexion JDBC sont :
Nom de la propriété |
Rôle |
hibernate.connection.driver_class |
nom pleinement qualifié de la classe du pilote JDBC |
hibernate.connection.url |
URL JDBC désignant la base de données |
hibernate.connection.username |
nom de l'utilisateur pour la connexion |
hibernate.connection.password |
mot de passe de l'utilisateur |
hibernate.connection.pool_size |
nombre maximum de connexions dans le pool |
Les principales propriétés pour configurer une source de données (DataSource) à utiliser sont :
Nom de la propriété |
Rôle |
hibernate.connection.datasource |
nom du DataSource enregistré dans JNDI |
hibernate.jndi.url |
URL du fournisseur JNDI |
hibernate.jndi.class |
classe pleinement qualifiée de type InitialContextFactory permettant l'accès à JNDI |
hibernate.connection.username |
nom de l'utilisateur de la base de données |
hibernate.connection.password |
mot de passe de l'utilisateur |
Les principales autres propriétés sont :
Nom de la propriété |
Rôle |
hibernate.dialect |
nom de la classe pleinement qualifiée qui assure le dialogue avec la base de données |
hibernate.jdbc.use_scrollable_resultset |
booléen qui permet le parcours dans les deux sens pour les connexions fournies à Hibernate utilisant des pilotes JDBC 2 supportant cette fonctionnalité |
hibernate.show_sql |
booléen qui précise si les requêtes SQL générées par Hibernate sont affichées dans la console (particulièrement utile lors du débogage) |
Hibernate propose des classes qui héritent de la classe Dialect pour chaque base de données supportée. C'est le nom de la classe correspondant à la base de données utilisée qui doit être obligatoirement fourni à la propriété hibernate.dialect.
Pour définir les propriétés utiles, le plus simple est de définir un fichier de configuration qui en standard doit se nommer hibernate.properties. Ce fichier contient des paires clé=valeur pour chaque propriété définie.
| Exemple : paramètres pour utiliser une base de données MySQL |
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect hibernate.connection.driver_class=com.mysql.jdbc.Driver hibernate.connection.url=jdbc:mysql://localhost/testDB hibernate.connection.username=root hibernate.connection.password= |
Le pilote de la base de données utilisée, mysql-connector-java-3.0.11-stable-bin.jar dans l'exemple, doit être ajouté dans le classpath.
Il est aussi possible de définir les propriétés dans un fichier au format XML nommé en standard hibernate.cfg.xml
Les propriétés sont alors définies par un tag <property>. Le nom de la propriété est fourni grâce à l'attribut « name » et sa valeur est fourni dans le corps du tag.
| Exemple : |
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/testDB</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">true</property>
<mapping resource="Personnes.hbm.xml"/>
</session-factory>
</hibernate-configuration> |
Pour utiliser Hibernate dans le code, il est nécessaire de réaliser plusieurs opérations :
Si les propriétés sont définies dans le fichier hibernate.properties, il faut tout d'abord créer une instance de la classe Configuration. Pour lui associer la ou les classes encapsulant les données, la classe propose deux méthodes :
Une instance de la classe Session est obtenue à partir d'une fabrique de type SessionFactory. Cet objet est obtenu à partir de l'instance du type Configuration en utilisant la méthode buildSessionFactory().
La méthode openSession() de la classe SessionFactory permet d'obtenir une instance de la classe Session.
Par défaut, c'est la méthode openSession() qui va ouvrir une connexion vers la base de données en utilisant les informations fournies par les propriétés de configuration.
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.Date;
public class TestHibernate1 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
... |
Il est aussi possible de fournir en paramètre de la méthode openSession() une instance de la classe javax.sql.Connection qui encapsule la connexion à la base de données.
Pour une utilisation du fichier hibernate.cfg.xml, il faut créer une occurrence de la classe Configuration, appeler sa méthode configure() qui va lire le fichier XML et appeler la méthode buildSessionFactory() de l'objet renvoyé par la méthode configure().
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
public class TestHibernate1 {
public static void main(String args[]) throws Exception {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
... |
Il est important de clôturer l'objet Session, une fois que celui-ci est devenu inutile, en utilisant la méthode close().
Pour créer une nouvelle occurrence dans la source de données, il suffit de créer une nouvelle instance de la classe encapsulant les données, de valoriser ses propriétés et d'appeler la méthode save() de la session en lui passant en paramètre l'objet encapsulant les données.
La méthode save() n'a aucune action directe sur la base de données. Pour enregistrer les données dans la base, il faut réaliser un commit sur la connexion ou la transaction ou faire appel à la méthode flush() de la classe Session.
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.Date;
public class TestHibernate1 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Personnes personne = new Personnes("nom3", "prenom3", new Date());
session.save(personne);
session.flush() ;
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw e;
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate1
Buildfile: build.xml
init:
[copy] Copying 1 file to C:\java\test\testhibernate\bin
compile:
TestHibernate1:
[java] 12:41:37,402 INFO Environment:462 - Hibernate 2.1.2
[java] 12:41:37,422 INFO Environment:496 - loaded properties from resource
hibernate.properties: {hibernate.connection.username=root, hibernate.connection
.password=, hibernate.cglib.use_reflection_optimizer=true, hibernate.dialect=net
.sf.hibernate.dialect.MySQLDialect, hibernate.connection.url=jdbc:mysql://localh
ost/testDB, hibernate.connection.driver_class=com.mysql.jdbc.Driver}
[java] 12:41:37,432 INFO Environment:519 - using CGLIB reflection optimizer
[java] 12:41:37,502 INFO Configuration:329 - Mapping resource: Personnes.hbm.xml
[java] 12:41:38,784 INFO Binder:229 - Mapping class: Personnes -> personnes
[java] 12:41:38,984 INFO Configuration:595 - processing one-to-many association mappings
[java] 12:41:38,994 INFO Configuration:604 - processing one-to-one association property
references
[java] 12:41:38,994 INFO Configuration:629 - processing foreign key constraints
[java] 12:41:39,074 INFO Dialect:82 - Using dialect: net.sf.hibernate.dialect.MySQLDialect
[java] 12:41:39,084 INFO SettingsFactory:62 - Use outer join fetching: true
[java] 12:41:39,104 INFO DriverManagerConnectionProvider:41 - Using Hibernate
built-in connection pool (not for production use!)
[java] 12:41:39,114 INFO DriverManagerConnectionProvider:42 - Hibernate co
nnection pool size: 20
[java] 12:41:39,144 INFO DriverManagerConnectionProvider:71 - using driver
: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost/testDB
[java] 12:41:39,154 INFO DriverManagerConnectionProvider:72 - connection p
roperties: {user=root, password=}
[java] 12:41:39,185 INFO TransactionManagerLookupFactory:33 - No Transacti
onManagerLookup configured (in JTA environment, use of process level read-write
cache is not recommended)
[java] 12:41:39,625 INFO SettingsFactory:102 - Use scrollable result sets:true
[java] 12:41:39,635 INFO SettingsFactory:105 - Use JDBC3 getGeneratedKeys(): true
[java] 12:41:39,635 INFO SettingsFactory:108 - Optimize cache for minimal puts: false
[java] 12:41:39,635 INFO SettingsFactory:117 - Query language substitutions: {}
[java] 12:41:39,645 INFO SettingsFactory:128 - cache provider: net.sf.ehcache.hibernate.
Provider
[java] 12:41:39,685 INFO Configuration:1080 - instantiating and configuring caches
[java] 12:41:39,946 INFO SessionFactoryImpl:119 - building session factory
[java] 12:41:41,237 INFO SessionFactoryObjectFactory:82 - no JNDI name configured
[java] 12:41:41,768 INFO SessionFactoryImpl:531 - closing
[java] 12:41:41,768 INFO DriverManagerConnectionProvider:137 - cleaning up
connection pool: jdbc:mysql://localhost/testDB
BUILD SUCCESSFUL
Total time: 7 seconds
C:\java\test\testhibernate> |
La méthode load() de la classe Session permet d'obtenir une instance de la classe encapsulant les données de l'occurrence de la base dont l'identifiant est fourni en paramètre.
Il existe deux surcharges de la méthode :
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
public class TestHibernate2 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
try {
Personnes personne = (Personnes) session.load(Personnes.class, new Integer(3));
System.out.println("nom = " + personne.getNomPersonne());
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate2
Buildfile: build.xml
init:
compile:
[javac] Compiling 1 source file to C:\java\test\testhibernate\bin
TestHibernate2:
[java] nom = nom3
BUILD SUCCESSFUL
Total time: 9 seconds |
Hibernate utilise plusieurs moyens pour obtenir des données de la base de données :
Pour offrir un langage d'interrogation commun à toutes les base de données, Hibernate propose son propre langage nommé HQL (Hibernate Query Language).
L'intérêt de HQL est d'être indépendant de la base de données sous jacente : la requête SQL sera générée par Hibernate à partir du HQL en fonction de la base de données précisée via un dialect.
Hibernate Query Language (HQL) est un langage de requêtes orienté objets qui permet de représenter des requêtes SQL : les entités utilisées dans les requêtes HQL sont des objets et des propriétés. La syntaxe de HQL et ses fonctionnalités de base sont très similaire à SQL.
Il est aussi possible d'utiliser l'API Criteria qui va en interne exécuter une requête HQL. Le point d'entrée est d'obtenir une instance de type Criteria en invoquant la méthode createCriteria() de la session Hibernate courante : elle attend en paramètre la classe des objets attendus en résultat. L'API permet de préciser les différents critères qui seront utilisés pour générer la requête. L'API Criteria utilise HQL en sous jacent.
Hibernate propose aussi un support pour exécuter des requêtes natives. Ceci permet d'utiliser des fonctionnalités de la base de données sous jacente qui ne soient pas supportées par HQL. Cependant dans ce cas, le support multi-base de données offert par HQL sera probablement compromis.
Le langage HQL est proche de SQL avec une utilisation sous forme d'objets des noms de certaines entités : il n'y a aucune référence aux tables ou aux champs car ceux-ci sont référencés respectivement par leur classe et leurs propriétés. C'est Hibernate qui se charge de générer la requête SQL à partir de la requête HQL en tenant compte du contexte (type de base de données utilisée défini dans le fichier de configuration et la configuration du mapping).
HQL possède une syntaxe similaire de celle de SQL : la différence majeure est que HQL utilise des objets et leurs propriétés alors que SQL utilise des tables et leurs colonnes
Exception faite des noms de classes et de variables, les requêtes HQL ne sont pas sensibles à la casse. Généralement les mots clé HQL sont en minuscule pour faciliter leur lecture.
Une requête HQL peut être composée :
Les clauses sont les mots clés HQL qui sont utilisés pour définir la requête :
|
Clause |
Description |
Syntaxe |
Exemple |
|
from |
précise la classe d'objets dont les occurrences doivent être retrouvées. Il est possible de définir un alias pour un objet en utilisant le mot clé alias |
from object [as objectalias] |
from Personne as pers (retourne toutes les occurrences de type Personne) |
|
select |
précise les propriétés à renvoyer. Doit être utilisé avec une clause from |
select pers.nom from Personne as pers (retourne le nom de toutes les personnes) |
|
|
where |
précise une condition qui permet de filtrer les occurrences retournées. Doit être utilisé avec une clause select et/ou from |
where condition |
from Personne as pers where pers.nom = "Dupond" (retourne toutes les personnes dont le nom est Dupond. |
|
order by |
précise un ordre de tri sur une ou plusieurs propriétés. L'ordre par défaut est ascendant |
order by propriete [asc|desc] [, propriete] ...; |
select pers.nom, pers.prenom from Personne as pers order by pers.nom asc, pers.prenom desc |
|
group by |
précise un critère de regroupement pour les résultats retournés. Doit être utilisé avec une clause select et/ou from |
group by propriete [, propriete] ... |
Les fonctions d'agrégation HQL ont un rôle similaire à celles de SQL : elles permettent de calculer des valeurs agrégeant des valeurs de propriétés issues du résultat de la requête.
|
Fonction |
Syntaxe |
|
count |
count( [distinct|all|*] object | object.property ) |
|
sum |
sum( [distinct|all] object.property ) |
|
avg |
avg( [distinct|all] object.property ) |
|
max |
max( [distinct|all] object.property ) |
|
min |
min( [distinct|all] object.property ) |
| Résultat : |
select avg(emp.salaire) from Employe as emp |
Les sous requêtes sont des requêtes imbriquées dans une autre requête
L'utilisation de sous requêtes dans HQL est conditionné par le support des sous requêtes par la base de données sous jacente. Les sous requêtes sont entourées par des parenthèses : elles sont exécutées avant la requête principale puisque celle-ci a besoin des résultats pour son exécution.
| Résultat : |
from Employe as emp where emp.salaire >= (select avg(Employe.salaire) from Employe) |
La mise en oeuvre de HQL peut se faire de plusieurs manières.
Le plus courant est d'obtenir une instance de la classe Query en invoquant la méthode createQuery() de la session Hibernate courante : elle attend en paramètre la requête HQL qui devra être exécutée.
| Exemple : |
Session session;
Query query = session.createQuery("select pers.nom from Personne as pers");
List result = query.list(); |
La méthode list() de la classe Query permet d'obtenir une collection qui contient les résultats de l'exécution de la requête.
Il est également possible de définir des requêtes utilisant des paramètres nommés grâce à un objet implémentant l'interface Query. Dans ces requêtes, les paramètres sont précisés avec un caractère « : » suivi d'un nom unique.
L'interface Query propose de nombreuses méthodes setXXX() pour associer à chaque paramètre une valeur en fonction du type de la valeur (XXX représente le type). Chacune de ces méthodes possède deux surcharges permettant de préciser le paramètre (à partir de son nom ou de son index dans la requête) et sa valeur.
Pour parcourir la collection des occurrences trouvées, l'interface Query propose la méthode list() qui renvoie une collection de type List ou la méthode iterate() qui renvoie un itérateur sur la collection.
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
public class TestHibernate8 {
public static void main(String args[]) throws Exception {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
try {
Query query = session.createQuery("from Personnes p where p.nomPersonne = :nom");
query.setString("nom", "nom2");
Iterator personnes = query.iterate();
while (personnes.hasNext()) {
Personnes personne = (Personnes) personnes.next();
System.out.println("nom = " + personne.getNomPersonne());
}
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate8
Buildfile: build.xml
init:
[copy] Copying 1 file to C:\java\test\testhibernate\bin
compile:
TestHibernate8:
[java] nom = nom2
BUILD SUCCESSFUL
Total time: 7 seconds |
La méthode find() de la classe Session permet d'effectuer une recherche d'occurrences grâce à la requête fournie en paramètre.
| Exemple : rechercher toutes les occurrences d'une table |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
public class TestHibernate3 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
try {
List personnes = session.find("from Personnes");
for (int i = 0; i < personnes.size(); i++) {
Personnes personne = (Personnes) personnes.get(i);
System.out.println("nom = " + personne.getNomPersonne());
}
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate3
Buildfile: build.xml
init:
compile:
[javac] Compiling 1 source file to C:\java\test\testhibernate\bin
TestHibernate3:
[java] nom = nom1
[java] nom = nom2
[java] nom = nom3
BUILD SUCCESSFUL
Total time: 14 seconds |
La méthode find() possède deux surcharges pour permettre de fournir un seul ou plusieurs paramètres dans la requête.
La première surcharge permet de fournir un seul paramètre : elle attend en paramètre la requête, la valeur du paramètre et le type du paramètre.
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
public class TestHibernate4 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
try {
List personnes = session.find("from Personnes p where p.nomPersonne=?",
"nom1", Hibernate.STRING);
for (int i = 0; i < personnes.size(); i++) {
Personnes personne = (Personnes) personnes.get(i);
System.out.println("nom = " + personne.getNomPersonne());
}
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate4
Buildfile: build.xml
init:
compile:
[javac] Compiling 1 source file to C:\java\test\testhibernate\bin
TestHibernate4:
[java] nom = nom1
BUILD SUCCESSFUL |
Dans la requête du précédent exemple, un alias nommé « p » est défini pour la classe Personnes. Le mode de fonctionnement d'un alias est similaire en HQL et en SQL.
La classe Session propose une méthode iterate() dont le mode de fonctionnement est similaire à la méthode find() mais elle renvoie un itérateur (objet de type Iterator) sur la collection des éléments retrouvés plutôt que la collection elle même.
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
public class TestHibernate6 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
try {
Iterator personnes = session.iterate("from Personnes ");
while (personnes.hasNext()) {
Personnes personne = (Personnes) personnes.next();
System.out.println("nom = " + personne.getNomPersonne());
}
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate6
Buildfile: build.xml
init:
compile:
[javac] Compiling 1 source file to C:\java\test\testhibernate\bin
TestHibernate6:
[java] nom = nom1
[java] nom = nom2
[java] nom = nom3
BUILD SUCCESSFUL |
Il est aussi possible d'utiliser la clause « order by » dans une requête HQL pour définir l'ordre de tri des occurrences.
Exemple :
List personnes = session.find("from Personnes p order by p.nomPersonne desc");
Il est possible d'utiliser des fonctions telles que count() pour compter le nombre d'occurrences.
| Exemple : |
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
public class TestHibernate5 {
public static void main(String args[]) throws Exception {
Configuration config = new Configuration();
config.addClass(Personnes.class);
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
try {
int compteur = ( (Integer) session.iterate(
"select count(*) from Personnes").next() ).intValue();
System.out.println("compteur = " + compteur);
} finally {
session.close();
}
sessionFactory.close();
}
} |
| Résultat : |
C:\java\test\testhibernate>ant TestHibernate5
Buildfile: build.xml
init:
compile:
[javac] Compiling 1 source file to C:\java\test\testhibernate\bin
TestHibernate5:
[java] compteur = 3
BUILD SUCCESSFUL |
Hibernate propose également d'externaliser une requête dans le fichier de mapping.
En plus du langage HQL pour réaliser des requêtes d'extraction de données, Hibernate propose une API qui permet de construire des requêtes pour interroger la base de données. L'API Criteria d'Hibernate propose donc une alternative à HQL sous la forme d'une API.
Le HQL possède une syntaxe dérivée de celle de SQL dans laquelle les notions relationnelles sont remplacées par des notions objets. Ceci oblige les développeurs à utiliser une syntaxe proche de celle du SQL.
L'API Criteria Query propose des objets pour définir les critères d'une requête ce qui permet aux développeurs de les définir d'une manière orienté objet plutôt que d'utiliser le HQL.
L'API Criteria propose donc d'avoir une approche orientée objet pour permettre de définir des requêtes pour obtenir des données. L'utilisation de cette API permet d'avoir un meilleur contrôle grâce à la compilation.
Cette API permet de facilement combiner de nombreux critères optionnels pour créer une requête : elle est particulièrement adaptée pour créer dynamiquement des requêtes à la volée comme c'est le cas par exemple pour des requêtes effectuant des recherches multicritères à partir d'informations fournies par l'utilisateur.
Elle offre pour la plupart des fonctionnalités une approche bi directionnelle :
Elle propose des classes et des interfaces qui encapsulent les fonctionnalités de SQL dont les principales sont :
L'interface org.hibernate.criterion.Criteria est le point d'entrée pour utiliser l'API Criteria. Elle permet de définir une requête à partir de critères pour retrouver des données.
| Exemple : |
select * from Personne |
Voici le code équivalent avec l'API Criteria :
| Exemple : |
List personnes = session.createCriteria(Personne.class).list(); |
Les critères de recherche permettant de restreindre les données retournées par la requête sont définis par l'interface org.hibernate.criterion.Criterion. Le type Criterion encapsule un élément de la clause "where" de la requête SQL qui sera générée.
| Exemple : |
Criteria criteria = session.createCriteria(Personne.class);
Criterion critere = Restrictions.eq("id", 2l);
criteria.add(critere);
List personnes = criteria.list();
System.out.println("nb personnes = " + personnes.size());
Iterator it = personnes.iterator();
while (it.hasNext()) {
Personne personne = (Personne) it.next();
System.out.println("Personne : " + personne);
}
|
La classe org.hibernate.criterion.Restrictions est une fabrique qui propose des méthodes statiques pour créer des instances de type Criterion
Depuis la version 3.x d'Hibernate, il est préférable d'utiliser la classe Restrictions à sa classe fille Expressions.
L'interface org.hibernate.criterion.Projection encapsule un champ en réponse de la requête (un champ dans la clause "select" de la requête SQL).
La classe org.hibernate.criterion.Projections est une fabrique pour les instances de type Projection.
| Exemple : |
SELECT nom FROM Personne |
| Exemple : |
List
noms = session.createCriteria(Personne.class)
.setProjection(Projections.property("nom"))
.list();
System.out.println("nb personnes = " + noms.size());
Iterator it = noms.iterator();
while (it.hasNext()) {
String nom = (String) it.next();
System.out.println("Personne : " + nom);
} |
La classe Order encapsule une clause SQL "order by".
L'API Criteria permet de définir à la volée des recherches de données multi-critères complexes. Un exemple typique d'utilisation où cette API est particulièrement utile est la recherche de données multi-critères où elle va permettre de facilement construire dynamiquement les critères à appliquer en fonction de ceux précisés par l'utilisateur de l'application.
Ceci est d'autant plus vrai que le nombre de critères optionnels est important : la génération dynamique peut alors devenir particulièrement complexe surtout si elle implique des jointures conditionnées par les critères précisés.
L'approche traditionnelle consiste à construire dynamiquement la requête HQL par concaténation des chaînes de caractères qui correspondent à chaque critère renseigné avec généralement plusieurs problématiques à gérer :
| Exemple : |
public List rechercher(Session session,
String nom,
String prenom,
Date dateDeb,
Date dateFin,
Integer taille) {
Map<String, Object> parametres = new HashMap<String, Object>();
boolean premiereClause = true;
StringBuffer requeteBuffer = new StringBuffer("from Personne p ");
if (nom != null) {
requeteBuffer.append(premiereClause ? "where " : " and ");
requeteBuffer.append("p.nom = :nom");
parametres.put("nom", nom);
premiereClause = false;
}
if (prenom != null) {
requeteBuffer.append(premiereClause ? " where " : " and ");
requeteBuffer.append("p.prenom = :prenom");
parametres.put("prenom", prenom);
premiereClause = false;
}
if (dateDeb != null) {
requeteBuffer.append(premiereClause ? " where " : " and ");
requeteBuffer.append("p.dateNais >= :dateDeb");
parametres.put("dateDeb", dateDeb);
premiereClause = false;
}
if (dateFin != null) {
requeteBuffer.append(premiereClause ? " where " : " and ");
requeteBuffer.append("p.dateNais <= :dateFin");
parametres.put("endDate", dateFin);
premiereClause = false;
}
if (taille != null) {
requeteBuffer.append(premiereClause ? " where " : " and ");
requeteBuffer.append("p.taille = :taille");
parametres.put("taille", taille);
premiereClause = false;
}
String requeteHql = requeteBuffer.toString();
Query query = session.createQuery(requeteHql);
Iterator<String> iter = parametres.keySet().iterator();
while (iter.hasNext()) {
String name = iter.next();
Object value = parametres.get(name);
query.setParameter(name, value);
}
return query.list();
}
|
Cette approche est lourde et source d'erreurs car le code contient des portions similaires répétées. Il est aussi très important de ne pas concaténer directement de valeurs saisies par l'utilisateur dans la requête.
L'API Criteria propose une solution plus propre, plus concise et plus sûre.
| Exemple : |
public List rechercher(Session session,
String nom,
String prenom,
Date dateDeb,
Date dateFin,
Integer taille) {
Criteria criteria = session.createCriteria(Personne.class);
if (dateDeb != null) {
criteria.add(Restrictions.ge("dateNais", dateDeb));
}
if (dateFin != null) {
criteria.add(Restrictions.le("dateNais", dateFin));
}
if (nom != null) {
criteria.add(Restrictions.eq("nom", nom));
}
if (prenom != null) {
criteria.add(Restrictions.eq("prenom", prenom));
}
if (taille != null) {
criteria.add(Restrictions.eq("taille", taille));
}
List resultat = criteria.list();
return resultat;
}
|
La quantité de code nécessaire en utilisant l'API Criteria est beaucoup moins importante que la quantité nécessaire à la construction dynamique de la requête HQL.
L'interface org.hibernate.Criteria propose des fonctionnalités pour encapsuler une requête composée de critères.
Une instance de l'interface Criteria est obtenue en invoquant la méthode createCriteria() de la session hibernate. Elle attends en paramètre la classe d'une entité sur laquelle les critères vont s'appliquer.
L'interface Criteria propose différentes méthodes pour construire les critères d'interrogation sur une classe persistante.
|
Méthode |
Rôle |
|
Criteria add(Criterion criterion) |
ajouter un critère Criteria |
|
addOrder(Order order) |
ajouter un ordre de tri |
|
Criteria createAlias(String associationPath, String alias) |
ajouter une jointure en lui assignant un alias |
|
Criteria createAlias(String associationPath, String alias, int joinType) |
créer un objet de type Criteria pour une entité donnée en précisant son type et en lui assignant un alias |
|
Criteria createCriteria(String associationPath) |
créer un objet de type Criteria pour une entité donnée |
|
Criteria createCriteria(String associationPath, int joinType) |
créer un objet de type Criteria pour une entité donnée en précisant son type de jointure |
|
Criteria createCriteria(String associationPath, String alias) |
créer un objet de type Criteria pour une entité donnée en lui assignant un alias |
|
Criteria createCriteria(String associationPath, String alias, int joinType) |
créer un objet de type Criteria pour une entité donnée en précisant son type, en lui assignant un alias et en précisant son type de jointure |
|
String getAlias() |
obtenir l'alias de l'entité encapsulée dans le Criteria |
|
List list() |
obtenir les résultats de la requête |
|
ScrollableResults scroll() |
obtenir les résultats comme une instance de type ScrollableResults |
|
ScrollableResults scroll(ScrollMode scrollMode) |
obtenir les résultats comme une instance de type ScrollableResults en précisant le mode de parcours |
|
Criteria setCacheable(boolean cacheable) |
activer ou non la mise en cache des résultats de la requête |
|
Criteria setCacheMode(CacheMode cacheMode) |
modifier le mode de mise en cache des résultats de la requête |
|
Criteria setCacheRegion(String cacheRegion) |
préciser le nom de la région du cache à utiliser pour stocker les résultats de la requête |
|
Criteria setComment(String comment) |
ajouter un commentaire à la requête SQL |
|
Criteria setFetchMode(String associationPath, FetchMode mode) |
préciser le mode de récupération des données dans le cas d'une association entre deux entités |
|
Criteria setFetchSize(int fetchSize) |
préciser le nombre d'occurences retournées par la requête |
|
Criteria setFirstResult(int firstResult) |
préciser la première occurence qui sera retournée |
|
Criteria setFlushMode(FlushMode flushMode) |
préciser le mode de flush de la requête |
|
Criteria setLockMode(LockMode lockMode) |
préciser le mode de verrou de l'entité |
|
Criteria setLockMode(String alias, LockMode lockMode) |
préciser le mode de verrou pour l'entité dont l'alias est fourni en paramètre |
|
Criteria setMaxResults(int maxResults) |
assigner un nombre maximum d'occurences retournées dans le résultat |
|
Criteria setProjection(Projection projection) |
préciser le contenu du résultat de la requête |
|
Criteria setResultTransformer(ResultTransformer resultTransformer) |
préciser une stratégie de traitement des résultats de la requête |
|
Criteria setTimeout(int timeout) |
assigner un timeout à la requête |
|
Object uniqueResult() |
ne renvoyer qu'une seule instance en résultat de la requête ou null si la requête ne renvoie aucun résultat. |
| Exemple : |
Criteria criteria = session.createCriteria(Personne.class);
criteria.setMaxResults(10);
List personnes = criteria.list(); |
L'interface org.hibernate.criterion.Criterion définit les méthodes pour un élément qui va permettre de définir un critère à appliquer dans la requête.
Chaque instance d'un critère doit être ajouté aux critères de la requête en utilisant la méthodes add() de l'instance de type Criteria.
L'API propose plusieurs fabriques qui permettent d'instancier les différents objets qui vont définir le contenu de la requête.
| Exemple : |
List personnes = session.createCriteria(Personne.class)
.add(Restrictions.like("nom", "Dup%"))
.add(Restrictions.gt("taille", new Integer(180)))
.addOrder(Order.asc("age"))
.list(); |
La classe org.hibernate.criterion.Restrictions est une fabrique qui propose des méthodes pour obtenir différentes instances de Criterion.
| Exemple : |
Calendar cal = Calendar.getInstance();
cal.set(1980, Calendar.JANUARY, 01);
Date dateDeb = cal.getTime();
cal.set(1980, Calendar.DECEMBER, 31);
Date dateFin = cal.getTime();
personnes = session.createCriteria(Personne.class)
.add(Restrictions.ge("dateNais", dateDeb))
.add(Restrictions.le("dateNais", dateFin))
.addOrder(Order.asc("dateNais"))
.setFirstResult(0)
.setMaxResults(10)
.list(); |
L'interface Criterion possède de nombreuses interfaces filles : AbstractEmptinessExpression, BetweenExpression, Conjunction, Disjunction, EmptyExpression, Example, ExistsSubqueryExpression, IdentifierEqExpression, IlikeExpression, InExpression, Junction, LikeExpression, LogicalExpression, NaturalIdentifier, NotEmptyExpression, NotExpression, NotNullExpression, NullExpression, PropertyExpression, PropertySubqueryExpression, SimpleExpression, SimpleSubqueryExpression, SizeExpression, SQLCriterion, SubqueryExpression.
La classe org.hibernate.criterion.Restrictions permet de créer des critères qui sont des conditions permettant de sélectionner les données à retrouver.
La classe Restrictions est une fabrique qui permet de créer des critères de recherche sous la forme d'instance de type Criterion. Les critères proposés encapsulent les opérateurs SQL standards.
Elle propose des méthodes statiques pour créer des instances des différentes implémentations de l'interface Criterion proposées par Hibernate. Ces critères sont utilisés pour définir quels seront les occurences qui seront retournées par le résultat de la requête.
| Exemple : |
SELECT * FROM Personne WHERE IdPers=1; |
| Exemple : |
List personnes = session.createCriteria(Personne.class)
.add(Restrictions.eq("id", 1l))
.list(); |
Les conditions standards de SQL sont encapsulées dans des objets de type Criterion : pour obtenir une de leur instance, il faut utiliser les méthodes statiques de la classe Restrictions dont les principales sont :
|
Méthode |
Rôle |
|
Criterion allEq(Map properties) |
Méthode utilitaire qui permet de facilement vérifier que plusieurs propriétés ont une valeur particulière. Les clés du paramètre de type Map correspondent aux noms des propriétés concernées |
|
LogicalExpression and(Criterion lhs, Criterion rhs) |
Créer un critère de type "and" qui est vrai si les deux critères sont évalués à vrai |
|
Criterion between(String propertyName, Object lo, Object hi) |
Permet d'appliquer une contrainte SQL de type "between" : la valeur de la propriété dont le nom est fourni en paramètre doit être comprise entre les deux valeurs fournies |
|
Conjunction conjunction() |
Créer un objet de type Conjunction qui permet d'utiliser un critère de type and simplement en invoquant sa méthode add() pour chaque critère à prendre en compte. Le critère encapsulé dans l'objet de type Conjunction sera true si tous les critères qu'il contient sont true |
|
Disjunction disjunction() |
Créer un objet de type Disjunction qui permet d'utiliser un critère de type or simplement en invoquant sa méthode add() pour chaque critère à prendre en compte. Le critère encapsulé dans l'objet de type Disjunction sera true si au moins un des critères qu'il contient est true |
|
SimpleExpression eq(String propertyName, Object value) |
Permet d'appliquer une contrainte SQL de type égalité : la valeur de la propriété doit être égale à la valeur fournie en paramètre |
|
PropertyExpression eqProperty(String propertyl, String property2) |
La valeur des deux propriétés doit être égale |
|
SimpleExpression ge(String propertyName, Object value) |
Permet d'appliquer une contrainte SQL de type "supérieur ou égal" : la valeur de la propriété doit être supérieure ou égale à la valeur fournie en paramètre |
|
PropertyExpression geProperty(String propertyl, String property2) |
La valeur de la propriété doit être supérieure ou égale à la valeur de la seconde propriété fournie en paramètre |
|
SimpleExpression gt(String propertyName, Object value) |
Permet d'appliquer une contrainte SQL de type "supérieur à" : la valeur de la propriété doit être supérieure à la valeur fournie en paramètre |
|
PropertyExpression gtProperty(String propertyl, String property2) |
La valeur de la propriété doit être supérieure à la valeur de la seconde propriété fournie en paramètre |
|
Criterion idEq(Object value) |
permet d'appliquer une contrainte SQL de type égalité sur l'identifiant : la valeur de la propriété qui est l'identifiant doit être égale à celle fournie |
|
Criterion ilike(String property, Object value) |
Joue le même rôle que la méthode like() mais en étant insensible à la casse |
|
Criterion ilike(String property, String value, MatchMode mode) |
Joue le même rôle que la méthode like() mais en étant insensible à la casse et sans utiliser la syntaxe de l'opérateur like. MatchMode est une énumération qui peut prendre les valeurs START, END, ANYWHERE, ou EXACT. |
|
Criterion in(String property, Collection values) |
La valeur de la propriété dont le nom est fourni en paramètre doit être égale à l'une de celles fournies dans la collection |
|
Criterion in(String propertyName, Collection values) |
Permet d'appliquer une contrainte SQL de type "in" : la valeur de la propriété dont le nom est fourni en paramètre doit être égale à l'une de celles fournies dans le tableau |
|
Criterion isEmpty(String property) |
Le contenu de la collection de la propriété dont le nom est fourni en paramètre ne doit pas avoir d'éléments |
|
Criterion isNotEmpty(String property) |
Le contenu de la collection de la propriété dont le nom est fourni en paramètre doit avoir au moins un élément |
|
Criterion isNotNull(String propertyName) |
Permet d'appliquer une contrainte SQL de type "is not null" : la valeur de la propriété dont le nom est fourni en paramètre doit être non null |
|
Criterion isNull(String propertyName) |
Permet d'appliquer une contrainte SQL de type "is null" : la valeur de la propriété dont le nom est fourni en paramètre doit être null |
|
SimpleExpression le(String property, Object value) |
La valeur de la propriété dont le nom est fourni en paramètre doit être inférieur ou égal à la valeur fournie |
|
PropertyExpression leProperty(String propertyl, String property2) |
La valeur de la propriété fournie en paramètre doit être inférieur ou égal à la valeur fournie |
|
SimpleExpression like(String property, Object value) |
La valeur de la propriété dont le nom est fourni en paramètre doit respecter le motif de l'opérateur sql like fourni en paramètre |
|
SimpleExpression like(String property, String value, MatchMode mode) |
La valeur de la propriété dont le nom est fourni en paramètre doit respecter le motif de l'opérateur sql like déterminé à partir des paramètre valeur et mode. MatchMode est une énumération qui peut prendre les valeurs START, END, ANYWHERE, ou EXACT. |
|
SimpleExpression lt(String property, Object value) |
La valeur de la propriété doit être inférieure à la valeur fournie en paramètre |
|
PropertyExpression ltProperty(String propertyl, String property2) |
La valeur de la première propriété doit être inférieure à la seconde |
|
SimpleExpression ne(String propertyName, Object value) |
Permet d'appliquer une contrainte SQL de type "est différent de" : la valeur de la propriété dont le nom est fourni en paramètre doit être différente de la valeur fournie |
|
PropertyExpression neProperty(String propertyName, String otherPropertyName) |
La valeur des deux propriétés fournies en paramètres doit être différente |
|
Criterion not(Criterion expression) |
L'évaluation du critère fourni en paramètre doit être false |
|
LogicalExpression or(Criterion lhs, Criterion rhs) |
L'évaluation d'un des deux critères fourni en paramètre doit être true |
|
Criterion sqlRestriction(String sql) |
Appliquer une restriction en sql natif |
|
Criterion sqlRestriction(String sql, Object[] values, Type[] types) |
Appliquer une restriction en sql natif qui va utiliser les paramètres fournis |
|
Criterion sqlRestriction(String sql, Object value, Type type) |
Appliquer une restriction en sql natif qui va utiliser le paramètre fourni |
Certaines de ces méthodes attendent un paramètre de type Criterion qui permet de faire des combinaisons de critères.
Les opérateurs de comparaison sont encapulés dans des méthodes de la classe Restrictions : eq(), lt(), le(), gt(), ge().
| Exemple : |
List personnes = session.createCriteria(Personne.class)
.add(Restrictions.lt("dateNais", dateSaisie))
.list(); |
La classe Restrictions propose aussi des méthodes pour les opérateurs SQL : like, between, in, is null, is not null ...
| Exemple : |
List personnes = session.createCriteria(Personne.class)
.add(Restrictions.between("dateNais", dateDeb, dateFin))
.add(Restrictions.like("nom", "Dup%"))
.list(); |
La classe Restrictions propose aussi des méthodes qui permettent de faire des comparaisons entre propriétés.
| Exemple : |
List personnes = session.createCriteria(Personne.class)
.add(Restrictions.eqProperty("nom", "prenom"))
.list(); |
L'utilisation de la méthode sqlRestriction() peut être très pratique mais elle peut nuire à la portabilité entre bases de données.
Pour préciser l'alias de la table courante dans la portion de requête SQL fournie en paramètre de la méthode sqlRestriction(), il faut utiliser la syntaxe "{alias}".
Il est possible d'utiliser les méthodes or() et and() pour réaliser des combinaisons de critères.
| Exemple : |
List personnes = session.createCriteria(Personne.class)
.add(Restrictions.or(Restrictions.eq("prenom", "Jean"),
Restrictions.eq("prenom", "Paul")))
.list();
|
Remarque : il est préférable d'utiliser la classe Restrictions plutôt que sa classe fille Expressions qui est deprecated.
La classe org.hibernate.criterion.Projection permet de préciser un champ qui sera retourné dans le résultat de la requête : ce champ peut être issu d'une table, du calcul d'une aggrégation, de la définition d'un alias, ...
Pour ajouter un champ, il faut passer le nom du champ en paramètre de la méthode statique property() de la classe Projection. L'instance retournée est passée en paramètre de la méthode setProjection().
La classe org.hibernate.criterion.Projections est une fabrique pour créer des instances de type Projection.
Pour préciser plusieurs champs, il faut utiliser la méthode propertyList() de la classe ProjectionList.
| Exemple : |
SELECT NOM, PRENOM FROM PERSONNE |
| Exemple : |
List resultats = session.createCriteria(Personne.class)
.setProjection(Projections.projectionList()
.add(Projections.property("nom"))
.add(Projections.property("prenom")))
.list();
System.out.println("nb personnes = " + resultats.size());
Iterator it = resultats.iterator();
while (it.hasNext()) {
Object[] donnees = (Object[]) it.next();
System.out.println("Nom : " + donnees[0]
+ " Prenom : " + donnees[1]);
}
|
Pour appliquer une collection de type Projection aux critères de la requête, il faut utiliser la méthode setProjection() de la classe Criteria. Une collection d'objets de type Projection est encapsulée dans un objet de type ProjectionList. La méthode add() permet d'ajouter une Projection à la collection.
| Exemple : |
List resultats = session.createCriteria(Personne.class)
.setProjection(Projections.rowCount())
.list();
Long valeur = (Long) resultats.get(0);
System.out.println("nb personnes = " + valeur); |
Les données de certaines requêtes doivent parfois être groupées ou intervenir dans un calcul d'aggrégation : il faut pour cela utiliser les fonctionnalités encapsulées dans la classe Projections. Toutes les fonctions d'aggrégation de la classe Projections sont des méthodes statiques.
La classe Projections possède plusieurs méthodes statiques :
|
Méthode |
Rôle |
|
static Projections alias(Projection projection, String alias) |
Assigner un alias |
|
static AgregateProjection avg(String property) |
Calculer la moyenne du champ dont le nom est fourni en paramètre |
|
static CountProjection count(String property) |
Calculer le nombre d'occurences du champ dont le nom et fourni en paramètre |
|
static CountProjection countDistinct(String property) |
Calculer le nombre d'occurences distinctes du champ dont le nom et fourni en paramètre |
|
static Projection distinct(Projection projection) |
Ne retourner que des valeurs uniques (supprimer les valeurs en doublon). |
|
static PropertyProjection groupProperty(String property) |
Grouper les résultats sur la propriété fournie |
|
static IdentifierProjection id() |
Renvoyer l'identifiant |
|
static AggregateProjection max(String property) |
Déterminer la plus grande valeur pour le champ dont le nom est fourni en paramètre |
|
static AggregateProjection min(String property) |
Déterminer la plus petite valeur pour le champ dont le nom est fourni en paramètre |
|
static ProjectionList projectionList() |
Retourner une collection de projections |
|
static PropertyProjection property(String property) |
Ajouter la propriété fournie en paramètre |
|
static Projection rowCount() |
Calculer le nombre d'occurrences retournées par la requête |
|
static Projection sqlGroupProjection(String sql, String groupBy, String[] columnAliases, Type[] types) |
Ajouter du code SQL spécifique à la base de données utilisée pour déterminer un groupage. Le paramètre groupBy peut contenir une clause GROUP BY |
|
static Projection sqlProjection(String sql, String[] columnAliases, Type[] types) |
Ajouter du code SQL spécifique à la base de données utilisée pour déterminer la liste de champs retournée par la requête |
|
static AggregateProjection sum(String property) |
Calculer la sommes des valeurs pour le champ dont le nom est fourni en paramètre |
Lors de l'utilisation d'opérateurs d'aggrégation, il est fréquent de grouper les données par rapport à un champ particulier. La classe Projections prossède la méthode groupProperty() qui permet de définir une clause "group by" qui sera utilisée avec le nom du champ fourni en paramètre.
| Exemple : |
SELECT COUNT(ID) FROM PERSONNE GROUP BY TAILLE |
| Exemple : |
List resultats = session.createCriteria(Personne.class)
.setProjection(Projections.projectionList()
.add(Projections.count("id"))
.add(Projections.groupProperty("taille")))
.list();
Iterator it = resultats.iterator();
while (it.hasNext()) {
Object[] donnees = (Object[]) it.next();
System.out.println("Nombre : " + donnees[0] + " taille : " + donnees[1]);
}
|
La classe org.hibernate.criterion.Property est une fabrique qui permet de créer des critères spécifiques appliqués à la propriété encapsulée. Ceci permet d'appliquer des critères directement sur une propriété.
La méthode statique forName() de la classe Property permet d'obtenir une instance qui encapsule une propriété.
La classe org.hibernate.criterion.Property propose plusieurs méthodes pour appliquer des critères sur la propriété qu'elle encapsule.
Les principales méthodes sont :
|
Méthode |
Rôle |
|
Order asc() |
Trier les valeurs de la propriété dans un ordre ascendant |
|
AggregateProjection avg() |
Créer un champ qui calcule la moyenne des occurences de la propriété |
|
Criterion between(Object min, Object max) |
Créer un critère qui requiert que la valeur de la propriété soit comprise entre les valeurs min et max fournies en paramètres |
|
CountProjection count() |
Créer un champ qui compte le nombre d'occurences de la propriété |
|
Order desc() |
Trier les valeurs de la propriété dans un ordre descendant |
|
SimpleExpression eq(Object value) |
Créer un critère pour filtrer les résultats de façon à ne retourner que les occurences dont la valeur de la propriété soit égale à celle fournie en paramètre |
|
PropertyExpression eqProperty(Property other) |
Créer un critère qui requiert que la valeur de la propriété soit égale à celle de la valeur de la propriété fournie en paramètre |
|
PropertyExpression eqProperty(String property) |
Créer un critère qui requiert que la valeur de la propriété soit égale à celle de la valeur de la propriété dont le nom est fourni en paramètre |
|
SimpleExpression ge(Object value) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure ou égale à celle fournie en paramètre |
|
PropertyExpression geProperty(Property other) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure ou égale à celle de la valeur de la propriété fournie en paramètre |
|
PropertyExpression geProperty(String property) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure ou égale à celle de la valeur de la propriété dont le nom est fourni en paramètre |
|
PropertyProjection group() |
Demander un groupage sur les valeurs de la propriété |
|
SimpleExpression gt(Object value) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure à celle fournie en paramètre |
|
PropertyExpression gtProperty(Property other) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure à celle de la valeur de la propriété fournie en paramètre |
|
PropertyExpression gtProperty(String property) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure à celle de la valeur de la propriété dont le nom est fourni en paramètre |
|
Criterion in(Collection values) |
Créer un critère qui requiert que la valeur de la propriété doit être égale à une de celles fournies |
|
in(Object[] values) |
Créer un critère qui requiert que la valeur de la propriété doit être égale à une de celles fournies |
|
Criterion isEmpty() |
Créer un critère qui requiert que la valeur de la propriété ne possède aucun élément |
|
Criterion isNotEmpty() |
Créer un critère qui requiert que la valeur de la propriété possède au moins un élément |
|
Criterion isNotNull() |
Créer un critère qui requiert que la valeur de la propriété ne soit pas null |
|
Criterion isNull() |
Créer un critère qui requiert que la valeur de la propriété soit null |
|
SimpleExpression le(Object value) |
Créer un critère qui requiert que la valeur de la propriété soit inférieure ou égale à celle fournie en paramètre |
|
PropertyExpression leProperty(Property other) |
Créer un critère qui requiert que la valeur de la propriété soit inférieure ou égale à celle de la valeur de la propriété fournie en paramètre |
|
PropertyExpression leProperty(String property) |
Créer un critère qui requiert que la valeur de la propriété soit inférieure ou égale à celle de la valeur de la paramètre dont le nom est fourni en paramètre |
|
SimpleExpressionlike(Object value) |
Créer un critère qui requiert que la valeur de la propriété respecte le motif fourni au sens de l'opérateur SQL like |
|
like(String value, MatchMode mode) |
Créer un critère qui requiert que la valeur de la propriété respecte un motif fourni au sens de l'opérateur SQL like Le motif est construit à partir de la sous chaine fournie en paramètre et de son mode recherche qui peut prendre les valeurs START, END, ANYWHERE, and EXACT. Ceci permet d'éviter d'avoir à manipuler la syntaxe de l'opérateur SQL like. |
|
SimpleExpression lt(Object value) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure ou égale à celle fournie en paramètre |
|
PropertyExpression ltProperty(Property other) |
Créer un critère qui requiert que la valeur de la propriété soit supérieure ou égale à celle de la valeur de la propriété fournie en paramètre |
|
PropertyExpression ltProperty(String property) |
Créer un critère qui requiert que la valeur de la propriété soit inférieure ou égale à celle de la valeur de la paramètre dont le nom est fourni en paramètre |
|
AggregateProjection max() |
Créer un champ qui va contenir la valeur la plus élevée de la propriété |
|
AggregateProjection min() |
Créer un champ qui va contenir la valeur la moins élevée de la propriété |
|
PropertyProjection ne(Object value) |
Créer un critère qui requiert que la valeur de la propriété soit différente de celle fournie en paramètre |
|
PropertyProjection neProperty(Property other) |
Créer un critère qui requiert que la valeur de la propriété soit différent de celle de la valeur de la propriété fournie en paramètre |
|
PropertyExpression neProperty(String property) |
Créer un critère qui requiert que la valeur de la propriété soit différente de celle de la valeur de la paramètre dont le nom est fourni en paramètre |
Il est possible de demander le tri des résultats de la requête en utilisant la méthode addOrderO de la classe Criteria et la classe Order.
La classe org.hibernate.criterion.Order permet d'encapsuler une clause de tri dans la requête en précisant le sens (ascendant ou descendant) sur un champ.
Les méthodes asc() et desc() permettent respectivement de définir un ordre ascendant ou descendant sur les résultats de la requête.
Elle possède plusieurs méthodes :
|
Méthode |
Rôle |
|
static Order asc(String) |
permet de demander un tri ascendant sur le nom du champ fourni en paramètre de la méthode |
|
static Order desc(String) |
permet de demander un tri descendant sur le nom du champ fourni en paramètre de la méthode |
|
Order ignoreCase() |
permet de demander d'ignorer la casse lors du tri des données |
| Exemple : |
List resultats = session.createCriteria(Personne.class)
.add(Restrictions.between("dateNais",dateDeb,dateFin))
.addOrder(Order.desc("dateNais"))
.addOrder(Order.asc("nom"));
|
Il est très fréquent d'avoir à effectuer une ou plusieurs jointures sur les tables d'une requête pour obtenir les données souhaitées.
En SQL, la jointure se fait en utilisant les tables dans la clause from et en indiquant les conditions de la jointure.
| Exemple : |
SELECT P.*, A.* FROM Personne P, Adresse A WHERE P.adresse_id=A.id AND A.Nom = »Dupond'; |
En HQL, il est possible de charger les données d'objets dépendants en utilisant la clause "left join fetch" :
| Exemple : |
from Personne personne where Personne.nom = :nom left join fetch personne.adresse |
La méthode setFetchMode() permet de faire une jointure avec l'API Criteria.
| Exemple : |
package com.jmdoudoux.test.hibernate;
import java.util.Iterator;
import java.util.List;
import org.hibernate.FetchMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
public class TestHibernateCriteria {
public static void main(String args[]) {
SessionFactory sessionFactory = new AnnotationConfiguration().configure()
.buildSessionFactory();
Transaction transaction = null;
List personnes = null;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
personnes = session.createCriteria(Personne.class)
.setFetchMode("adresse", FetchMode.JOIN)
.list();
System.out.println("nbpersonnes = " + personnes.size());
Iterator it = personnes.iterator();
while (it.hasNext()) {
Personne personne = (Personne) it.next();
System.out.println("Personne : " + personne);
System.out.println(" Adresse : " + personne.getAdresse());
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
La méthode setFetchMode() attend deux paramètres :
Parfois, il est nécessaire de joindre les entités mais il est inutile de récupérer les données de l'entité jointe : dans ce cas la jointure n'est utile que pour définir un ou plusieurs critères de la requête
| Exemple : |
from Personne p join p.adresse a where a.cp = '54000' |
Il est possible d'utiliser la méthode createCriteria() pour créer une jointure :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import java.util.Iterator;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.criterion.Restrictions;
public class TestHibernateCriteria {
public static void main(String args[]) {
SessionFactory sessionFactory = new AnnotationConfiguration().configure()
.buildSessionFactory();
Transaction transaction = null;
List personnes = null;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
personnes = session.createCriteria(Personne.class)
.createCriteria("adresse", "a")
.add(Restrictions.eq("a.cp", "54700"))
.list();
System.out.println("nb personnes = " + personnes.size());
Iterator it = personnes.iterator();
while (it.hasNext()) {
Personne personne = (Personne) it.next();
System.out.println("Personne : " + personne);
System.out.println(" Adresse: " + personne.getAdresse());
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Attention : dans ce cas, le premier paramètre de la méthode createCriteria() est le nom d'une propriété et non le nom de classe.
Il est aussi possible d'utiliser la méthode createAlias() qui évite d'avoir à créer une nouvelle instance de la classe Criteria.
| Exemple : |
personnes = session.createCriteria(Personne.class)
.createAlias("adresse", "a")
.add(Restrictions.eq("a.cp", "54000"))
.list();
|
Les restrictions sont aussi utilisables avec une jointure dans laquelle les données de la table jointe sont récupérées.
| Exemple : |
SELECT G.*, P.* FROM Personnes P, Groupe G WHERE G.IdGroup=P.IdGroup AND G.IdGroup=l; |
| Exemple : |
List groupes = session.createCriteria(Groupe.class)
.setFetchMode("Personne",FetchMode.JOIN)
.add(Restrictions.eq("IdGroup","1"))
.list(); |
La classe Example permet de créer des critères par l'exemple : pour cela, il faut instancier une entité et lui affecter les différentes valeurs qui seront utilisées comme critères.
La méthode static create() qui attend en paramètre une instance d'une entité va utiliser l'instrospection pour rechercher les propriétés qui possèdent une valeur et générer une instance de type Example contenant les critères correspondants aux propriétés renseignées.
| Exemple : |
transaction = session.beginTransaction();
Date dateDebut = new GregorianCalendar(1980, Calendar.JANUARY, 01).getTime();
Date dateFin = new GregorianCalendar(1981, Calendar.JANUARY, 01).getTime();
Personne personneExemple = new Personne();
personneExemple.setNom("Dupond");
personneExemple.setPrenom("Michel");
Example example = Example.create(personneExemple).ignoreCase().excludeZeroes();
List<Personne> resultats = session.createCriteria(Personne.class)
.add(example)
.add(Restrictions.between("dateNais",
dateDebut, dateFin)).list();
System.out.println("nbpersonnes = " + personnes.size());
Iterator it = personnes.iterator();
while (it.hasNext()) {
Personne personne = (Personne) it.next();
System.out.println("Personne : " + personne);
System.out.println(" Adresse : " + personne.getAdresse());
}
transaction.commit(); |
Il est possible de configurer le comportement de la classe Example en utilisant les différentes méthodes qu'elle propose à cette fin :
|
Méthode |
Rôle |
|
Example enableLike() |
Utiliser l'opérateur like pour toutes les propriétés de type String |
|
Example enableLike(MatchMode matchMode) |
Utiliser l'opérateur like pour toutes les propriétés de type String avec la stratégie de correspondance fournie en paramètre |
|
Example excludeNone() |
Ne pas exclure les propriétés dont la valeur est null ou zéro |
|
Example excludeProperty(String name) |
Ignorer la propriété dont le nom est fourni en paramètre |
|
Example excludeZeroes() |
Ignorer les propriétés dont la valeur est zéro |
|
Example ignoreCase() |
Ignorer la casse des propriétés de type String |
|
Example setEscapeCharacter(Character escapeCharacter) |
Préciser le caractère d'échappement utilisé dans la clause like |
Cette API est particulièrement utile si le nombre de propriétés est important. Comme la classe Example hérite de la classe Criterion, elle peut être utilisée pour créer les critères "simple" et utiliser d'autres classes pour les critères plus complexes ou spécifiques.
L'API Criteria est très puissante et elle est particulièrement bien adaptée pour certaines tâches (notamment la création de requête dynamique à la volée impliquant de nombreux critères optionnels comme par exemple dans un formulaire de recherche multi critères).
Dans ces cas, sa mise en oeuvre peut permettre d'avoir un code pour propre, plus sûre et plus maintenable.
Cependant, son utilisation ne peut pas toujours être généralisée à tous les cas de figure car l'utilisation de HQL est parfois préférable, notamment si la requête n'est pas dynamique.
Il est par exemple préférable d'externaliser les requêtes HQL lorsque cela est possible car cela possède plusieurs avantages :
Il est donc nécessaire de bien choirsir entre HQL et l'API Criteria en fonction des besoins et de leur adéquation avec ce que proposent les deux solutions qui se recouvrent mais sont aussi complémentaires.
Pour mettre à jour une occurrence dans la source de données, il suffit d'appeler la méthode update() de la session en lui passant en paramètre l'objet encapsulant les données.
Le mode de fonctionnement de cette méthode est similaire à celui de la méthode save().
La méthode saveOrUpdate() laisse Hibernate choisir entre l'utilisation de la méthode save() ou update() en fonction de la valeur de l'identifiant dans la classe encapsulant les données.
La méthode delete() de la classe Session permet de supprimer une ou plusieurs occurrences en fonction de la version surchargée de la méthode utilisée.
Pour supprimer une occurrence encapsulée dans une classe, il suffit d'invoquer la classe en lui passant en paramètre l'instance de la classe.
Pour supprimer plusieurs occurrences, voire toutes, il faut passer en paramètre de la méthode delete(), une chaîne de caractères contenant la requête HQL pour préciser les éléments concernés par la suppression.
| Exemple : suppression de toutes les occurrences de la table |
session.delete("from Personnes"); |
Un des fondements du modèle de données relationnelles repose sur les relations qui peuvent intervenir entre une table et une ou plusieurs autres tables ou la table elle même.
Les relations utilisables dans le monde relationnel et le monde objet sont cependant différentes.
Les relations du monde objets possèdent quelques caractéristiques :
Les relations du monde relationnel possèdent quelques caractéristiques :
Les caractéristiques de ces deux modèles sont assez différentes : le but d'un outil de type ORM comme Hibernate est de permettre de manipuler des entités objets et de masquer au développeur le monde relationnel en assurant un mapping entre les deux mondes. Cependant Hibernate ne garantit pas en automatique la gestion inverse des relations qui devra être à la charge du développeur.
Hibernate propose de transcrire les relations du modèle relationnel dans le modèle objet. Il supporte plusieurs types de relations :
Dans le fichier de mapping, il est nécessaire de définir les relations entre la table concernée et les tables avec lesquelles elle possède des relations.
Les relations peuvent aussi être définies avec des annotations.
La version d'Hibernate utilisée dans cette section est la 3.5.1.
La base de données utilisée est une base MySQL version 4.1.9.
Chaque exemple possède plusieurs bibliothèques dans son classpath : commons-collections-3.1.jar, dom4j-1.6.1.jar, hibernate-jpa-2.0-api-1.0.0.Final.jar, hibernate3.jar, javassist-3.9.0.GA.jar, jta-1.1.jar, log4j-1.2.15.jar, mysql-connector-java-5.1.12-bin.jar, slf4j-api-1.5.8.jar, slf4j-log4j12-1.5.11.jar.
Dans ce type de relation, deux entités sont liées de façon à n'avoir qu'une seule et unique occurrence l'une pour l'autre.

Dans l'exemple ci-dessus, chaque personne ne peut avoir qu'une seule adresse et une adresse ne peut appartenir qu'à une seule personne.
Cette relation peut se traduire de plusieurs manières dans la base de données :
Il y a plusieurs façons de traiter ce cas avec une ou deux tables dans la base de données et Hibernate :
Comme une personne ne peut avoir qu'une seule adresse, il est préférable pour des raisons de performance de stocker les données des deux entités dans une seule et même table. Ceci évite d'avoir à faire une jointure lors de l'accès aux données des deux entités.
La description de la table personne est la suivante :
| Résultat : |
mysql> desc personne; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | Id | int(11) | | PRI | NULL | auto_increment | | Nom | varchar(255) | | | | | | Prenom | varchar(255) | | | | | | DateNais | date | YES | | NULL | | | ligne1_adr | varchar(80) | | | | | | ligne2_adr | varchar(80) | YES | | NULL | | | cp_adr | varchar(5) | YES | | NULL | | | ville_adr | varchar(80) | YES | | NULL | | | ligne3_adr | varchar(80) | YES | | NULL | | +------------+--------------+------+-----+---------+----------------+ 9 rows in set (0.00 sec) |
Le script DDL correspondant est le suivant :
| Résultat : |
CREATE TABLE `personne` ( `Id` int(11) NOT NULL auto_increment, `Nom` varchar(255) NOT NULL default '', `Prenom` varchar(255) NOT NULL default '', `DateNais` date default NULL, `ligne1_adr` varchar(80) NOT NULL default '', `ligne2_adr` varchar(80) default NULL, `cp_adr` varchar(5) default NULL, `ville_adr` varchar(80) default NULL, `ligne3_adr` varchar(80) default NULL, PRIMARY KEY (`Id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ; |
Les classes qui encapsulent l'entité personne et les données de l'adresse sont de simples POJO.
| Exemple : |
package com.jmdoudoux.test.hibernate;
public class Personne {
private Long id;
private String nom;
private String prenom;
private String dateNais;
private Adresse adresse;
public Personne(String nom, String prenom, String dateNais, Adresse adresse) {
this.nom = nom;
this.prenom = prenom;
this.dateNais = dateNais;
this.adresse = adresse;
}
public Personne() {
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public String getDateNais() {
return dateNais;
}
public void setDateNais(String dateNais) {
this.dateNais = dateNais;
}
public Long getId() {
return id;
}
// Attention le setter est requis par Hibernate
public void setId(Long id) {
this.id = id;
}
public Adresse getAdresse() {
return adresse;
}
public void setAdresse(Adresse adresse) {
this.adresse = adresse;
}
@Override
public String toString() {
return this.id + " : " + this.nom + " " + this.prenom;
}
} |
La classe Adresse ne possède pas de champ de type identifiant.
| Exemple : |
package com.jmdoudoux.test.hibernate;
public class Adresse {
private String ligne1;
private String ligne2;
private String cp;
private String ville;
private String ligne3;
public Adresse(String ligne1, String ligne2, String cp, String ville, String ligne3) {
super();
this.ligne1 = ligne1;
this.ligne2 = ligne2;
this.cp = cp;
this.ville = ville;
this.ligne3 = ligne3;
}
public Adresse() {
}
//
// getter et setter sur les champs de la classe
//
} |
Les données de l'adresse sont encapsulées dans une classe Adresse : la définition des champs de cette classe est faite dans un élément de type component du fichier de mapping de l'entité Personne (Personne.hbm.xml).
| Exemple : |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.jmdoudoux.test.hibernate.Personne" table="Personne">
<id name="id" column="id">
<generator class="increment" />
</id>
<property name="nom" column="Nom" />
<property name="prenom" column="Prenom" />
<property name="dateNais" column="DateNais" />
<component name="adresse" class="com.jmdoudoux.test.hibernate.Adresse">
<property name="ligne1" column="ligne1_adr" />
<property name="ligne2" column="ligne2_adr" />
<property name="cp" column="cp_adr" />
<property name="ville" column="ville_adr" />
<property name="ligne3" column="ligne3_adr" />
</component>
</class>
</hibernate-mapping> |
Le fichier de configuration d'Hibernate définit les paramètres de connexion à la base de données et le fichier de mapping de l'entité Personne.
| Exemple : |
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/mabdd</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="com/jmdoudoux/test/hibernate/Personne.hbm.xml"></mapping>
</session-factory>
</hibernate-configuration> |
L'application de test est basique :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class TestHibernate15 {
public static void main(String args[]) {
SessionFactory sessionFactory = new Configuration().configure()
.buildSessionFactory();
Transaction transaction = null;
int index = 6;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
Adresse adresse = new Adresse("ligne1_" + index, "ligne2_" + index, "cp_"
+ index, "ville" + index, "ligne3_" + index);
Personne personne = new Personne("nom" + index,
"prenom_" + index,
null,
adresse);
session.save(personne);
transaction.commit();
System.out.println("La nouvelle personne a ete enregistree");
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Lorsque la personne est enregistrée dans la base de données, son adresse l'est aussi dans la table Personne.
| Résultat : |
mysql> select * from personne; +----+------+----------+----------+------------+------------+--------+---------- -+------------+ | Id | Nom | Prenom | DateNais | ligne1_adr | ligne2_adr | cp_adr | ville_adr | ligne3_adr | +----+------+----------+----------+------------+------------+--------+---------- -+------------+ | 1 | nom6 | prenom_6 | NULL | ligne1_6 | ligne2_6 | cp_6 | ville6 | ligne3_6 | +----+------+----------+----------+------------+------------+--------+---------- -+------------+ 1 row in set (0.00 sec) |
Le POJO qui encapsule une personne a quelques particularités relatives à la relation avec l'adresse :
Il possède un champ privé de type Adresse
Le champ adresse est annoté avec l'annotation @Embedded
| Exemple : |
package com.jmdoudoux.test.hibernate;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "personne")
public class Personne {
@Id
@GeneratedValue
@Column(name = "Id")
private Long id;
@Column(name = "Nom")
private String nom;
@Column(name = "Prenom")
private String prenom;
@Column(name = "DateNais")
private String dateNais;
@Embedded
private Adresse adresse;
public Personne(String nom, String prenom, String dateNais, Adresse adresse) {
this.nom = nom;
this.prenom = prenom;
this.dateNais = dateNais;
this.adresse = adresse;
}
public Personne() {
}
public Long getId() {
return id;
}
public Adresse getAdresse() {
return adresse;
}
public void setAdresse(Adresse adresse) {
this.adresse = adresse;
}
//
// getter et setter sur les autres champs de la classe
//
@Override
public String toString() {
return this.id + " : " + this.nom + " " + this.prenom;
}
} |
L'annotation @Embedded permet de préciser que les données de la classe Adresse seront stockées dans la table Personne comme un component d'Hibernate.
Le POJO qui encapsule une adresse possède plusieurs particularités relatives à la relation avec la personne :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class Adresse {
@Column(name = "ligne1_adr", nullable = false)
private String ligne1;
@Column(name = "ligne2_adr")
private String ligne2;
@Column(name = "cp_adr")
private String cp;
@Column(name = "ville_adr")
private String ville;
@Column(name = "ligne3_adr")
private String ligne3;
public Adresse(String ligne1, String ligne2, String cp, String ville,
String ligne3) {
super();
this.ligne1 = ligne1;
this.ligne2 = ligne2;
this.cp = cp;
this.ville = ville;
this.ligne3 = ligne3;
}
public Adresse() {
}
//
// getter et setter sur les champs de la classe
//
} |
L'annotation @Embeddable permet de préciser que la classe sera utilisée comme un component. Un tel élément n'a pas d'identifiant puisque celui utilisé sera celui de l'entité englobante.
Le fichier de configuration d'Hibernate définit les paramètres de connexion à la base de données et les deux classes qui encapsulent l'entité Personne et le component Adresse.
| Exemple : |
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/mabdd</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping class="com.jmdoudoux.test.hibernate.Personne"></mapping>
<mapping class="com.jmdoudoux.test.hibernate.Adresse"></mapping>
</session-factory>
</hibernate-configuration>
|
L'application de test est basique :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
public class TestHibernate16 {
public static void main(String args[]) {
SessionFactory sessionFactory = new AnnotationConfiguration().configure()
.buildSessionFactory();
Transaction transaction = null;
int index = 7;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
Adresse adresse = new Adresse("ligne1_" + index, "ligne2_" + index, "cp_"
+ index, "ville" + index, "ligne3_" + index);
Personne personne = new Personne("nom" + index,
"prenom_" + index,
null,
adresse);
session.save(personne);
transaction.commit();
System.out.println("La nouvelle personne a ete enregistree");
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Lorsque la personne est enregistrée dans la base de données, son adresse l'est aussi dans la table Personne.
| Résultat : |
mysql> select * from personne; +----+------+----------+----------+------------+------------+--------+---------- -+------------+ | Id | Nom | Prenom | DateNais | ligne1_adr | ligne2_adr | cp_adr | ville_adr | ligne3_adr | +----+------+----------+----------+------------+------------+--------+---------- -+------------+ | 2 | nom7 | prenom_7 | NULL | ligne1_7 | ligne2_7 | cp_7 | ville7 | ligne3_7 | +----+------+----------+----------+------------+------------+--------+---------- -+------------+ 1 row in set (0.00 sec) |
La relation repose sur deux tables distinctes : une pour les personnes et une pour les adresses.
Chacune des deux tables possède un identifiant qui est sa clé primaire. La particularité est que la valeur des clés primaires est partagée entre les deux tables. L'identifiant de la table adresse n'est pas auto incrémenté et correspond à la valeur de l'identifiant de la table personne.
Hibernate ne sait pas gérer seul ce type de mapping : il sera nécessaire de l'aider en utilisant un mapping bidirectionnel qui permettra à Hibernate de connaitre la valeur de l'identifiant de la personne à utiliser comme valeur de l'identifiant pour l'adresse afin que les deux correspondent.
La description de la table personne est la suivante :
| Résultat : |
mysql> desc personne; +----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+--------------+------+-----+---------+----------------+ | Id | bigint(20) | | PRI | NULL | auto_increment | | Nom | varchar(255) | | | | | | Prenom | varchar(255) | | | | | | DateNais | date | YES | | NULL | | +----------+--------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) |
Le script DDL correspondant est le suivant :
| Résultat : |
CREATE TABLE `personne` ( `Id` bigint(20) NOT NULL auto_increment, `Nom` varchar(255) NOT NULL default '', `Prenom` varchar(255) NOT NULL default '', `DateNais` date default NULL, PRIMARY KEY (`Id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ; |
La description de la table adresse est la suivante :
| Résultat : |
mysql> desc adresse; +------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+---------+-------+ | id | bigint(20) | | PRI | 0 | | | ligne1_adr | varchar(80) | | | | | | ligne2_adr | varchar(80) | YES | | NULL | | | cp_adr | varchar(5) | YES | | NULL | | | ville_adr | varchar(80) | YES | | NULL | | | ligne3_adr | varchar(80) | YES | | NULL | | +------------+-------------+------+-----+---------+-------+ 6 rows in set (0.11 sec) |
Le script DDL correspondant est le suivant :
| Résultat : |
CREATE TABLE `adresse` ( `id` bigint(20) NOT NULL default '0', `ligne1_adr` varchar(80) NOT NULL default '', `ligne2_adr` varchar(80) default NULL, `cp_adr` varchar(5) default NULL, `ville_adr` varchar(80) default NULL, `ligne3_adr` varchar(80) default NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; |
Les classes qui encapsulent les entités personne et adresse sont de simples POJO.
| Exemple : |
package com.jmdoudoux.test.hibernate;
public class Adresse {
private Long id;
private String ligne1;
private String ligne2;
private String cp;
private String ville;
private String ligne3;
private Personne personne;
public Adresse(String ligne1, String ligne2, String cp, String ville,
String ligne3) {
super();
this.ligne1 = ligne1;
this.ligne2 = ligne2;
this.cp = cp;
this.ville = ville;
this.ligne3 = ligne3;
}
public Adresse() {
}
public Long getId() {
return id;
}
// setter requis par Hibernate
public void setId(Long id) {
this.id = id;
}
public Personne getPersonne() {
return personne;
}
public void setPersonne(Personne personne) {
this.personne = personne;
}
//
// getter et setter sur les autres champs de la classe
//
} |
| Exemple : |
package com.jmdoudoux.test.hibernate;
public class Personne {
private Long id;
private String nom;
private String prenom;
private String dateNais;
private Adresse adresse;
public Personne(String nom, String prenom, String dateNais, Adresse adresse) {
this.nom = nom;
this.prenom = prenom;
this.dateNais = dateNais;
this.adresse = adresse;
}
public Personne() {
}
public Long getId() {
return id;
}
// Attention le setter est requis par Hibernate
public void setId(Long id) {
this.id = id;
}
public Adresse getAdresse() {
return adresse;
}
public void setAdresse(Adresse adresse) {
this.adresse = adresse;
}
//
// getter et setter sur les autres champs de la classe
//
@Override
public String toString() {
return this.id + " : " + this.nom + " " + this.prenom;
}
} |
Le fichier de mapping de l'entité Personne (Personne.hbm.xml) contient un élément fils <one-to-one> pour définir la relation entre Personne et Adresse.
| Exemple : |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.jmdoudoux.test.hibernate.Personne" table="Personne">
<id name="id" column="id">
<generator class="increment" />
</id>
<property name="nom" column="Nom" />
<property name="prenom" column="Prenom" />
<property name="dateNais" column="DateNais" />
<one-to-one name="adresse" class="com.jmdoudoux.test.hibernate.Adresse"
cascade="save-update" />
</class>
</hibernate-mapping>
|
Le fichier de mapping de l'entité Adresse (Adresse.hbm.xml) possède plusieurs caractéristiques liées au type de la relation utilisée avec l'entité Personne :
| Exemple : |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.jmdoudoux.test.hibernate.Adresse" table="Adresse">
<id name="id" column="Id">
<generator class="foreign">
<param name="property">personne</param>
</generator>
</id>
<property name="ligne1" column="ligne1_adr" />
<property name="ligne2" column="ligne2_adr" />
<property name="cp" column="cp_adr" />
<property name="ville" column="ville_adr" />
<property name="ligne3" column="ligne3_adr" />
<one-to-one name="personne" class="com.jmdoudoux.test.hibernate.Personne"
constrained="true" />
</class>
</hibernate-mapping>
|
Le fichier de configuration d'Hibernate définit les paramètres de connexion à la base de données et les deux fichiers de mapping des entités.
| Exemple : |
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/mabdd</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="com/jmdoudoux/test/hibernate/Personne.hbm.xml"></mapping>
<mapping resource="com/jmdoudoux/test/hibernate/Adresse.hbm.xml"></mapping>
</session-factory>
</hibernate-configuration>
|
L'application de test est basique :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class TestHibernate11 {
public static void main(String args[]) {
SessionFactory sessionFactory = new Configuration().configure()
.buildSessionFactory();
Transaction transaction = null;
int index = 3;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
Adresse adresse = new Adresse("ligne1_" + index, "ligne2_" + index, "cp_"
+ index, "ville" + index, "ligne3_" + index);
Personne personne = new Personne("nom" + index,
"prenom_" + index,
null,
adresse);
adresse.setPersonne(personne);
session.save(personne);
transaction.commit();
System.out.println("La nouvelle personne a ete enregistree");
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Lorsque la personne est enregistrée dans la base de données, son adresse l'est aussi avec comme identifiant la valeur de l'identifiant de la personne.
| Résultat : |
mysql> select * from personne; +----+------+----------+----------+ | Id | Nom | Prenom | DateNais | +----+------+----------+----------+ | 1 | nom3 | prenom_3 | NULL | +----+------+----------+----------+ 1 row in set (0.00 sec) mysql> select * from adresse; +----+------------+------------+--------+-----------+------------+ | id | ligne1_adr | ligne2_adr | cp_adr | ville_adr | ligne3_adr | +----+------------+------------+--------+-----------+------------+ | 1 | ligne1_3 | ligne2_3 | cp_3 | ville3 | ligne3_3 | +----+------------+------------+--------+-----------+------------+ 1 row in set (0.00 sec) |
Le POJO qui encapsule une personne a quelques particularités relatives à la relation avec l'adresse :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
@Entity
@Table(name = "personne")
public class Personne {
@Id
@GeneratedValue
@Column(name = "Id")
private Long id;
@Column(name = "Nom")
private String nom;
@Column(name = "Prenom")
private String prenom;
@Column(name = "DateNais")
private String dateNais;
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private Adresse adresse;
public Personne(String nom, String prenom, String dateNais, Adresse adresse) {
this.nom = nom;
this.prenom = prenom;
this.dateNais = dateNais;
this.adresse = adresse;
}
public Personne() {
}
public Long getId() {
return id;
}
public Adresse getAdresse() {
return adresse;
}
public void setAdresse(Adresse adresse) {
this.adresse = adresse;
}
//
// getter et setter sur les autres champs de la classe
//
@Override
public String toString() {
return this.id + " : " + this.nom + " " + this.prenom;
}
} |
Si le champ adresse n'est pas annoté avec l'annotation @PrimaryKeyJoin, alors une exception de type org.hibernate.id.IdentifierGenerationException avec le message « null id generated for:class com.jmdoudoux.test.hibernate.Adresse » est levée à l'exécution.
Le POJO qui encapsule une adresse possède plusieurs particularités relatives à la relation avec la personne :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.Parameter;
@Entity
@Table(name = "adresse")
public class Adresse {
@Id
@GeneratedValue(generator = "adresseGenerator")
@org.hibernate.annotations.GenericGenerator(name = "adresseGenerator",
strategy = "foreign", parameters = @Parameter(name = "property", value = "personne"))
@Column(name = "id")
private Long id;
@Column(name = "ligne1_adr", nullable = false)
private String ligne1;
@Column(name = "ligne2_adr")
private String ligne2;
@Column(name = "cp_adr")
private String cp;
@Column(name = "ville_adr")
private String ville;
@Column(name = "ligne3_adr")
private String ligne3;
@OneToOne(mappedBy = "adresse")
private Personne personne;
public Adresse(String ligne1, String ligne2, String cp, String ville,
String ligne3) {
super();
this.ligne1 = ligne1;
this.ligne2 = ligne2;
this.cp = cp;
this.ville = ville;
this.ligne3 = ligne3;
}
public Adresse() {
}
public Long getId() {
return id;
}
public Personne getPersonne() {
return personne;
}
public void setPersonne(Personne personne) {
this.personne = personne;
}
//
// getter et setter sur les autres champs de la classe
//
} |
Le fichier de configuration d'Hibernate définit les paramètres de connexion à la base de données et les deux classes qui encapsulent des entités.
| Exemple : |
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/mabdd</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping class="com.jmdoudoux.test.hibernate.Personne"></mapping>
<mapping class="com.jmdoudoux.test.hibernate.Adresse"></mapping>
</session-factory>
</hibernate-configuration>
|
L'application de test est basique :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
public class TestHibernate10 {
public static void main(String args[]) {
SessionFactory sessionFactory = new AnnotationConfiguration().configure()
.buildSessionFactory();
Transaction transaction = null;
int index = 2;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
Adresse adresse = new Adresse("ligne1_" + index, "ligne2_" + index, "cp_"
+ index, "ville" + index, "ligne3_" + index);
Personne personne = new Personne("nom" + index,
"prenom_" + index,
null,
adresse);
adresse.setPersonne(personne);
session.save(personne);
transaction.commit();
System.out.println("La nouvelle personne a ete enregistree");
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Lorsque la personne est enregistrée dans la base de données, son adresse l'est aussi avec comme identifiant la valeur de l'identifiant de la personne.
| Résultat : |
mysql> select * from adresse; +----+------------+------------+--------+-----------+------------+ | id | ligne1_adr | ligne2_adr | cp_adr | ville_adr | ligne3_adr | +----+------------+------------+--------+-----------+------------+ | 3 | ligne1_1 | ligne2_1 | cp_1 | ville1 | ligne3_1 | +----+------------+------------+--------+-----------+------------+ 1 row in set (0.00 sec) mysql> select * from personne; +----+------+----------+----------+ | Id | Nom | Prenom | DateNais | +----+------+----------+----------+ | 3 | nom1 | prenom_1 | NULL | +----+------+----------+----------+ 1 row in set (0.00 sec) |
Si la référence de l'instance de type Personne n'est pas fournie à l'instance de type Adresse alors une exception de type org.hibernate.id.IdentifierGenerationException avec le message « attempted to assign id from null one-to-one property [com.jmdoudoux.test.hibernate.Adresse.personne] » est levée à l'exécution.
Si le générateur d'identifiant n'est pas correctement configuré pour l'entité Adresse, alors une exception de type org.hibernate.id.IdentifierGenerationException avec le message « ids for this class must be manually assigned before calling save(): com.jmdoudoux.test.hibernate.Adresse » lors de l'exécution.
La relation repose sur deux tables distinctes : une pour les personnes et une pour les adresses
Chacune des deux tables possède son propre identifiant et la relation entre les deux tables est assurée par une clé étrangère de la table personne vers la table adresse.
Hibernate sait gérer seul ce type de mapping si il est unidirectionnel.
La description de la table personne est la suivante :
| Résultat : |
mysql> desc personne; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | Id | int(11) | | PRI | NULL | auto_increment | | Nom | varchar(255) | | | | | | Prenom | varchar(255) | | | | | | DateNais | date | YES | | NULL | | | adresse_id | int(11) | | | 0 | | +------------+--------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) |
Le script DDL correspondant est le suivant :
| Résultat : |
CREATE TABLE `personne` ( `Id` int(11) NOT NULL auto_increment, `Nom` varchar(255) NOT NULL default '', `Prenom` varchar(255) NOT NULL default '', `DateNais` date default NULL, `adresse_id` int(11) NOT NULL default '0', PRIMARY KEY (`Id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ; |
La description de la table adresse est la suivante :
| Résultat : |
mysql> desc adresse; +------------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+-------------+------+-----+---------+----------------+ | id | bigint(20) | | PRI | NULL | auto_increment | | ligne1_adr | varchar(80) | | | | | | ligne2_adr | varchar(80) | YES | | NULL | | | cp_adr | varchar(5) | YES | | NULL | | | ville_adr | varchar(80) | YES | | NULL | | | ligne3_adr | varchar(80) | YES | | NULL | | +------------+-------------+------+-----+---------+----------------+ 6 rows in set (0.00 sec) |
Le script DDL correspondant est le suivant :
| Résultat : |
CREATE TABLE `adresse` ( `id` bigint(20) NOT NULL auto_increment, `ligne1_adr` varchar(80) NOT NULL default '', `ligne2_adr` varchar(80) default NULL, `cp_adr` varchar(5) default NULL, `ville_adr` varchar(80) default NULL, `ligne3_adr` varchar(80) default NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ; |
Les classes qui encapsulent les entités personne et adresse sont de simples POJO.
| Exemple : |
package com.jmdoudoux.test.hibernate;
public class Personne {
private Long id;
private String nom;
private String prenom;
private String dateNais;
private Adresse adresse;
public Personne(String nom, String prenom, String dateNais, Adresse adresse) {
this.nom = nom;
this.prenom = prenom;
this.dateNais = dateNais;
this.adresse = adresse;
}
public Personne() {
}
public Long getId() {
return id;
}
// Attention le setter est requis par Hibernate
public void setId(Long id) {
this.id = id;
}
public Adresse getAdresse() {
return adresse;
}
public void setAdresse(Adresse adresse) {
this.adresse = adresse;
}
//
// getter et setter sur les autres champs de la classe
//
@Override
public String toString() {
return this.id + " : " + this.nom + " " + this.prenom;
}
} |
| Exemple : |
package com.jmdoudoux.test.hibernate;
public class Adresse {
private Long id;
private String ligne1;
private String ligne2;
private String cp;
private String ville;
private String ligne3;
public Adresse(String ligne1, String ligne2, String cp, String ville, String ligne3) {
super();
this.ligne1 = ligne1;
this.ligne2 = ligne2;
this.cp = cp;
this.ville = ville;
this.ligne3 = ligne3;
}
public Adresse() {
}
public Long getId() {
return id;
}
// setter requis par Hibernate
public void setId(Long id) {
this.id = id;
}
//
// getter et setter sur les autres champs de la classe
//
} |
Le fichier de mapping de l'entité Personne (Personne.hbm.xml) contient un élément fils <many-to-one> pour définir la relation entre Personne et Adresse : l'unicité de la relation est cependant garantie par la valeur true de l'attribut unique. Il faut aussi utiliser une propriété column pour préciser la colonne qui va contenir la clé étrangère vers la table adresse.
| Exemple : |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.jmdoudoux.test.hibernate.Personne" table="Personne">
<id name="id" column="Id">
<generator class="increment" />
</id>
<property name="nom" column="Nom" />
<property name="prenom" column="Prenom" />
<property name="dateNais" column="DateNais" />
<many-to-one name="adresse" class="com.jmdoudoux.test.hibernate.Adresse"
column="adresse_id" cascade="all" unique="true" />
</class>
</hibernate-mapping>
|
Le fichier de mapping de l'entité Adresse (Adresse.hbm.xml) ne contient aucune particularité.
| Exemple : |
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.jmdoudoux.test.hibernate.Adresse" table="Adresse">
<id name="id">
<generator class="increment" />
</id>
<property name="ligne1" column="ligne1_adr" />
<property name="ligne2" column="ligne2_adr" />
<property name="cp" column="cp_adr" />
<property name="ville" column="ville_adr" />
<property name="ligne3" column="ligne3_adr" />
</class>
</hibernate-mapping>
|
Le fichier de configuration d'Hibernate définit les paramètres de connexion à la base de données et les deux fichiers de mapping des entités.
| Exemple : |
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/mabdd</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="com/jmdoudoux/test/hibernate/Personne.hbm.xml"></mapping>
<mapping resource="com/jmdoudoux/test/hibernate/Adresse.hbm.xml"></mapping>
</session-factory>
</hibernate-configuration>
|
L'application de test est basique :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class TestHibernate12 {
public static void main(String args[]) {
SessionFactory sessionFactory = new Configuration().configure()
.buildSessionFactory();
Transaction transaction = null;
int index = 4;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
Adresse adresse = new Adresse("ligne1_" + index, "ligne2_" + index, "cp_"
+ index, "ville" + index, "ligne3_" + index);
Personne personne = new Personne("nom_" + index,
"prenom_" + index,
null,
adresse);
session.save(personne);
transaction.commit();
System.out.println("La nouvelle personne a ete enregistree");
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Lorsque la personne est enregistrée dans la base de données, son adresse l'est aussi. La nouvelle occurrence de la table Personne et de la table Adresse possède chacun leur propre identifiant et celui de l'adresse est reporté dans le champ adresse_id de la table Personne.
| Résultat : |
mysql> select * from personne; +----+-------+----------+----------+------------+ | Id | Nom | Prenom | DateNais | adresse_id | +----+-------+----------+----------+------------+ | 1 | nom_4 | prenom_4 | NULL | 8 | +----+-------+----------+----------+------------+ 1 row in set (0.82 sec) mysql> select * from adresse; +----+------------+------------+--------+-----------+------------+ | id | ligne1_adr | ligne2_adr | cp_adr | ville_adr | ligne3_adr | +----+------------+------------+--------+-----------+------------+ | 8 | ligne1_4 | ligne2_4 | cp_4 | ville4 | ligne3_4 | +----+------------+------------+--------+-----------+------------+ 1 row in set (0.00 sec) |
Le POJO qui encapsule une personne a quelques particularités relatives à la relation avec l'adresse :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
@Entity
@Table(name = "personne")
public class Personne {
@Id
@GeneratedValue
@Column(name = "Id")
private Long id;
@Column(name = "Nom")
private String nom;
@Column(name = "Prenom")
private String prenom;
@Column(name = "DateNais")
private String dateNais;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "adresse_id")
private Adresse adresse;
public Personne(String nom, String prenom, String dateNais, Adresse adresse) {
this.nom = nom;
this.prenom = prenom;
this.dateNais = dateNais;
this.adresse = adresse;
}
public Personne() {
}
public Long getId() {
return id;
}
public Adresse getAdresse() {
return adresse;
}
public void setAdresse(Adresse adresse) {
this.adresse = adresse;
}
//
// getter et setter sur les autres champs de la classe
//
@Override
public String toString() {
return this.id + " : " + this.nom + " " + this.prenom;
}
} |
Le POJO qui encapsule une adresse ne possède aucune particularité relative à la relation avec la personne. Il est cependant possible d'ajouter au besoin une relation inverse d'adresse vers personne en ajoutant un champ Personne annoté avec l'annotation @one-to-one possèdant un attribut mappedBy qui possède comme valeur le nom du champ de l'adresse dans l'entité Personne.
Dans ce cas, la gestion de l'alimentation du champ personne est à la charge du développeur en utilisant le setter sur le champ personne.
L'identifiant de l'entité est annoté avec @GeneratedValue
| Exemple : |
package com.jmdoudoux.test.hibernate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "adresse")
public class Adresse {
@Id
@GeneratedValue
@Column(name = "id")
private Long id;
@Column(name = "ligne1_adr", nullable = false)
private String ligne1;
@Column(name = "ligne2_adr")
private String ligne2;
@Column(name = "cp_adr")
private String cp;
@Column(name = "ville_adr")
private String ville;
@Column(name = "ligne3_adr")
private String ligne3;
public Adresse(String ligne1, String ligne2, String cp, String ville,
String ligne3) {
super();
this.ligne1 = ligne1;
this.ligne2 = ligne2;
this.cp = cp;
this.ville = ville;
this.ligne3 = ligne3;
}
public Adresse() {
}
public Long getId() {
return id;
}
//
// getter et setter sur les autres champs de la classe
//
} |
Le fichier de configuration d'Hibernate définit les paramètres de connexion à la base de données et les deux classes qui encapsulent des entités.
| Exemple : |
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/mabdd</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<mapping class="com.jmdoudoux.test.hibernate.Personne"></mapping>
<mapping class="com.jmdoudoux.test.hibernate.Adresse"></mapping>
</session-factory>
</hibernate-configuration>
|
L'application de test est basique :
| Exemple : |
package com.jmdoudoux.test.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
public class TestHibernate14 {
public static void main(String args[]) {
SessionFactory sessionFactory = new AnnotationConfiguration().configure()
.buildSessionFactory();
Transaction transaction = null;
int index = 5;
Session session = sessionFactory.openSession();
try {
transaction = session.beginTransaction();
Adresse adresse = new Adresse("ligne1_" + index, "ligne2_" + index, "cp_"
+ index, "ville" + index, "ligne3_" + index);
Personne personne = new Personne("nom" + index,
"prenom_" + index,
null,
adresse);
session.save(personne);
transaction.commit();
System.out.println("La nouvelle personne a ete enregistree");
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
session.close();
}
sessionFactory.close();
}
} |
Lorsque la personne est enregistrée dans la base de données, son adresse l'est aussi. Chaque nouvelle occurrence de la table Personne et de la table Adresse possède son propre identifiant et celui de l'adresse est reporté dans le champ adresse_id de la table Personne.
| Résultat : |
mysql> select * from adresse; +----+------------+------------+--------+-----------+------------+ | id | ligne1_adr | ligne2_adr | cp_adr | ville_adr | ligne3_adr | +----+------------+------------+--------+-----------+------------+ | 10 | ligne1_5 | ligne2_5 | cp_5 | ville5 | ligne3_5 | +----+------------+------------+--------+-----------+------------+ 1 row in set (0.00 sec) mysql> select * from personne; +----+-------+----------+----------+------------+ | Id | Nom | Prenom | DateNais | adresse_id | +----+-------+----------+----------+------------+ | 8 | nom5 | prenom_5 | NULL | 10 | +----+-------+----------+----------+------------+ 1 row in set (0.00 sec) |
![]() |
|
La suite de cette section sera développée dans une version future de ce document |
Hibernate fournit séparément un certain nombre d'outils. Ces outils sont livrés séparément dans un fichier nommé hibernate-extensions-2.1.zip.
Il faut télécharger et décompresser le contenu de cette archive par exemple dans le répertoire ou Hibernate a été décompressé.
L'archive contient deux répertoires :
Le répertoire tools propose trois outils :
Pour utiliser ces outils, il y a deux solutions possibles :
Pour utiliser les fichiers de commandes .bat, il est nécessaire au préalable de configurer les paramètres dans le fichier setenv.bat. Il faut notamment correctement renseigner les valeurs associées aux variables JDBC_DRIVER qui précise le pilote de la base de données et HIBERNATE_HOME qui précise le répertoire ou est installé Hibernate.
Pour utiliser les outils dans un script ant, il faut créer ou modifier un fichier build en ajoutant une tâche pour l'outil à utiliser.
Il faut copier le fichier hibernate2.jar et les fichiers contenus dans le répertoire /lib d'Hibernate dans le répertoire lib du projet. Il faut aussi copier dans ce répertoire les fichiers contenus dans le répertoire /tools/lib et le fichier /tools/hibernate-tools.jar.
Pour éviter les messages d'avertissement sur la configuration manquante de log4j, le plus simple est de copier le fichier /src/log4j.properties d'Hibernate dans le répertoire bin du projet.
![]() |
|
La suite de ce chapitre sera développée dans une version future de ce document |
|
|
|
|
|
|
Développons en Java v 1.60 | ||
| Copyright (C) 1999-2011 Jean-Michel DOUDOUX |