Intégration JSF et Hibernate dans JOnAS 4.0.0/ Tomcat 5.0.21

Jean-Louis Pasturel

Ce document est destiné à expliquer l'intégration de JSF et Hibernate dans JOnAS 4.0.0/ Tomcat 5.0.21

- Titre : Intégration JSF et Hibernate dans JOnAS 4.0.0/ Tomcat 5.0.21

- Auteur : Jean-Louis Pasturel

- Parution : 8 juillet 2004

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Jean-Louis Pasturel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Préambule

- I-A. Avertissement

- I-B. Prérequis

- I-B-1. Connaissances

- I-B-2. Logiciels

- I-B-3. Operating System utilisé et organisation des répertoires

- II. Architecture logicielle

- II-A. Schéma de l'architecture logicielle

- II-B. Description de l'application

- III. Construction de l'application

- III-A. Mécanisme de construction

- III-B. Partie database

- III-C. Partie Hibernate

- III-C-1. Qu'est-ce que HIBERNATE ?

- III-C-2. Installation du produit Hibernate

- III-C-3. Un exemple pour voir !

- III-C-3-a. La classe POJO POJOUtilisateur1

- III-C-3-b. Mettre à jour hibernate.properties

- III-C-3-c. Il faut écrire le fichier XML de mapping 0/R

- III-C-3-d. Client pour jouer avec POJOUtilisateur1

- III-C-3-e. Analyse du code

- III-C-3-f. Jouons un peu plus avec notre POJOUtilisateur1

- III-C-3-g. Récapitulation des opérations (cas manuel/sans ide)

- III-C-3-h. Un peu d'information sur l'aspect non threadsafe de la session

- III-C-4. Un exemple un peu plus complexe

- III-C-4-a. Rappel sur les Collections en Java et utilisation dans Hibernate

- III-C-4-b. Stratégie de création des tables de type "table per class hierarchy"

- III-C-4-c. Stratégie de création des tables de type « table per sub-class hierarchy »

- III-C-4-d. Stratégie de création des tables de type " table per concrete class "

- III-C-4-e. Les POJO utilisés dans notre exemple

- III-C-4-f. Les fichiers de mapping .hbm.xml correspondants.

- III-C-4-g. Génération du script de création de la base (outil SchemaExport)

- III-C-4-h. Création de la database à l'ide du script ddl généré

- III-C-4-i. Écriture des classes (Facade et Client) d'utilisation des POJO

- III-C-4-j. Premier test basique : création de Personnes à travers les sous-classes Externe et Employe

- III-C-4-k. Création de Users

- III-C-4-l. Création de rôles et associations avec des utilisateurs

- III-C-4-m. Puissance de la clause de cascade

- III-C-5. HQL et APICriteria

- III-C-5-a. Traitons d'abord le cas d'utilisation HQL :

- III-C-5-b. Traitement avec l'API Criteria

- III-C-6. Pour aller plus loin avec Hibernate

- III-D. Partie Java Server Faces

- III-D-1. Exemple simplifié

- III-D-1-a. Mise en place de Hibernate et des Java Server Faces dans le bundle Jonas4-Tomcat5

- III-D-1-b. Adaptation du package la classe POJOUtilisateur

- III-D-1-c. Navigation simpliste de l'application

- III-D-1-d. Les fichiers constituant la partie métier de la web application

- IV. L'application en images

- IV-A. Construction de l'application

- IV-B. Déploiement de l'application dans Jonas

- IV-C. Utilisation de l'application

I - Préambule

I-A - Avertissement

Les premiers mots d'un bébé quand il essaye de parler sont souvent « arheu ! Arheu ! ». C'est un peu dans cet esprit que j'écris ce tutoriel.
Les technologies étudiées dans ce tutoriel sont les JSF et Hibernate.
Ce document est écrit au fur et à mesure que je prends connaissance de cette technologie. Pour la partie Hibernate que j'étudierai en premier, je m'appuie sur la documentation de référenceHibernate, les forums Hibernate et quelques tutoriels référencés sur le site : http://www.hibernate.org.

Pour la partie Java Server Faces (JSF), je m'appuie évidemment fortement sur le tutoriel J2EE à regarder ensuite pour approfondir vos connaissances avec cette technologie. http://java.sun.com/j2ee/1.4/docs/index.html#tutorials.

I-B - Prérequis

I-B-1 - Connaissances

Connaître les technologies servlet et JSP/sevlet de J2EE (voir document pointé par l'URL de sun ci-dessus). Une connaissance minimale des architectures des serveurs web et conteneur de servlet est nécessaire pour la suite.

I-B-2 - Logiciels

La norme et l'implémentation de référence pour les Java Server Faces nécessitent d'avoir un conteneur de servlet au niveau de la norme servlet 2.3 ou 2.4. Nous allons prendre la norme servlet 2.4 qui est supportée par Tomcat V5.x (ainsi que JSP V2.0). Pour cela, il faut installer une version de TomcatV5. Nous allons utiliser le bundle Jonas4 Tomcat5 pour le mécanisme de déploiement convivial fourni par Jonas.

Pour télécharger Jonas, aller à l'URL : http://forge.objectweb.org/project/showfiles.php?group_id=5.

L'installation du bundle et le paramétrage ne sont pas décrits dans ce document, se référer à l'excellente documentation fournie avec Jonas.

Il faut aussi télécharger l'implémentation de référence de JSF 1.0. On va éviter de télécharger l'ensemble du J2EE, Sun met à disposition une implémentation « standalone » à l'URL suivante : http://java.sun.com/j2ee/javaserverfaces/download.html et choisir dans la page le téléchargement de l'implémentation de référence : JavaServer Faces v1.1 Reference Implementation.

Pour les besoins de stockage des données, nous utiliserons sur Linux une base Postgres en version 7.4. (il existe sous Windows une version équivalente avec le bundle cygwin).
Pour postgres voir le site : http://www.postgresql.org pour le téléchargement et l'installation de la base.

Pour l'accès à la base de données et la gestion de la persistance, j'utiliserai le logiciel de Mapping O/R et de gestion de la persistance Hibernate V 2.1.3.

I-B-3 - Operating System utilisé et organisation des répertoires

Tout ce qui est décrit par la suite est fait sur un poste équipé de LINUX V2.4.x et d'une machine virtuelle Java 1.4.2.
Comme Tomcat et Hibernate sont full Java, il n'y aura pas (beaucoup) de difficultés à faire l'extrapolation pour Windows.
Le jdk 1.4.2 est situé sous /usr/local/jdk142.
L'implémentation de référence JSF sera installée à part sous /opt/jsf-1_1 et nous servira de répertoire de développement de notre applicatif pour la partie jsf.
Le bundle Jonas4/Tomcat5 est installé sous le répertoire /opt/JONAS_4_0_0.

II - Architecture logicielle

II-A - Schéma de l'architecture logicielle

La cible de ce tutoriel, est de réaliser une petite application, que nous bâtirons pas à pas pour arriver à l'architecture décrite dans le schéma ci-dessous :

Figure : 1

Toutes les briques du schéma ci-dessus vont être décrites dans la suite du document. Par souci de simplification (et compte tenu de la taille de l'application !), nous faisons jouer le rôle de Business Delegate et de Session Façade, au même objet. Dans le cas d'une application de plus grande envergure, il faut décoller l'aspect Business de la présentation si on veut faciliter (à travers le même Business Delegate) l'accès aux services du composant Session Façade (à gros grains) en utilisant d'autres technologies (Client lourd, Corba, RMI, Web services…).

Dans nos exemples, je n'utiliserai pas la partie EJB V2.x, fournie par le conteneur Jonas. Je crois qu'il faut attendre l'intégration des spécifications EJB V3 en cours d'élaboration à ce jour (mai 2004) qui vont révolutionner complètement la gestion des EJB. Le modèle EJB V3 sera assez proche du schéma ci-dessus.

II-B - Description de l'application

Dans un premier temps, nous nous limiterons à une application basique qui gère les opérations de consultation, création, modification, suppression d'utilisateurs. Ces utilisateurs seront stockés dans une table unique, dont la structure suivante :

iduser

nom

prénom

password

datecm

issupprimable

PK : Numérique

chaîne

chaîne

chaîne

date (timestamp)

Numérique (1/0)

Au fur et à mesure de l'avancement de ce tutoriel, nous ferons évoluer ce modèle de données pour introduire des notions complémentaires.

III - Construction de l'application

III-A - Mécanisme de construction

La construction de l'application va se dérouler en partant de la database et en remontant vers la partie cliente.

III-B - Partie database

La table dans la base de données essai aura comme nom : utilisateurs.

Le script de création de la table utilisateurs est donné par createTableUtilisateurs.sql ci-dessous :

createTableUtilisateurs.sql

DROP TABLE utilisateurs;

DROP SEQUENCE seq_utilisateurs;

CREATE TABLE utilisateurs (

idUser       bigint CONSTRAINT CONS_idUser PRIMARY KEY,

nom       varchar(40) NOT NULL,

prenom      varchar(40) NOT NULL,

password    varchar(20) NOT NULL,

datecm   timestamp,

issupprimable int CHECK (issupprimable = 0 OR issupprimable = 1)

);

--

--Creation de la sequence seq_utilisateur pour idUser de la table utilisateurs

-- Attention ceci n'est pas dans la norme SQL

--

CREATE SEQUENCE seq_utilisateurs;

insert into utilisateurs values (

nextval('seq_utilisateurs'),'admin','admin','admin',CURRENT_TIMESTAMP,0);

Lancer ce script par :
connexion à la base essai : (en fonction de l'existence de contrôle, possibilité d'avoir à saisir un mot de passe, en principe si le fichier pg_hba.conf comporte une ligne qui considère que les connexions locales sont sures (trust en dernière colonne) on n'a pas à saisir de mot de passe).

psql essai

Lancer la commande :

\i <repertoire_createTableUtilisateurs.sql>/createTableUtilisateurs.sql

La table utilisateurs, la séquence seq_utilisateurs, l'index cons_iduser sont créés.

select * from utilisateurs;

iduser |  nom  | prenom | password |           datecm           | issupprimable

--------+-------+--------+----------+----------------------------+---------------

1 | admin | admin  | admin    | 2004-04-28 21:34:26.036093 |             0

ligne)

III-C - Partie Hibernate

Ce chapitre va être beaucoup plus long… Courage !

III-C-1 - Qu'est-ce que HIBERNATE ?

Hibernate est un logiciel, écrit en Java, qui permet de faire le mapping entre Objets Java et Objets stockés en base relationnelle. De plus il en assure la persistance.

Hibernate 2.1.3 possède les fonctionnalités d'annuaire JNDI d'enregistrement et localisation des objets, la gestion des transactions. Ce sont les fonctionnalités que nous examinerons dans ce tutoriel.

Hibernate a été écrit sous la responsabilité de Gavin King qui fait partie de l'équipe JBoss.
Site de référence pour récupérer le produit et la documentation :
http://www.hibernate.org/
Récupérer le Document de Référence Hibernate sur lequel s'appuie ce tutoriel.

III-C-2 - Installation du produit Hibernate

Après avoir récupéré l'archive, j'ai décompressé cette archive sous le répertoire /opt.
Le décompactage a créé l'arborescence /opt/hibernate-2.1.

Placer le driver JDBC de la base sous le répertoire /opt/hibernate-2.1/lib.
Pour postgres 7.4 il s'agit du fichier jar : pg74.1jdbc3.jar.

Modifier le fichier /opt/hibernate-2.1/src/hibernate.properties pour y intégrer les caractéristiques de la base postgresql :

Extrait :

## PostgreSQL

hibernate.dialect net.sf.hibernate.dialect.PostgreSQLDialect

hibernate.connection.driver_class org.postgresql.Driver

hibernate.connection.url jdbc:postgresql:essai

hibernate.connection.username pastjl

hibernate.connection.password

#hibernate.query.substitutions yes 'Y', no 'N'

revenir sous /opt/hibernate-2.1 et lancer

ant eg

Puis refaire le test en modifiant la valeur hibernate.show_sql=true dans le fichier /opt/hibernate-2.1/src/hibernate.properties.

##############################

### Miscellaneous Settings ###

##############################

## print all generated SQL to the console

hibernate.show_sql true

et regarder dans la console les traces générées et connectez-vous à la base pour vérifier la création de tables correspondant au code.
Si OK l'installation est correcte.

III-C-3 - Un exemple pour voir !

En nous inspirant de l'exemple fourni dans le manuel de référence Hibernate, nous allons l'adapter à notre table utilisateurs créée plus haut.
Le schéma de cet exemple logiciel sera le suivant :

Figure : 2

Le schéma ci-dessous permet de fixer la chaîne du dialogue, il ne faut y chercher aucun formalisme particulier.
Le ClientTest va nous permettre d'instancier le POJO, et ensuite de jouer avec le POJO, pour créer, modifier, supprimer des utilisateurs de la table utilisateur.
Mais POJO késako ?
Acronyme pour (selon les Auteurs) :

- Plain Old Java Object ;

- Plain Ordinary Java Object.

donc un simple Objet JavaBean, issu de la J2SE (Standard Edition).

Nous allons donc créer une classe Utilisateur1 que nous mapperons avec la table utilisateurs côté postgreSQL.

III-C-3-a - La classe POJO POJOUtilisateur1

Cette classe doit être un javabean, donc exposer comme propriété, les colonnes de la table. Ce ne sera pas toujours le cas, mais commençons par faire simple.
Vous trouverez ci-dessous le code complet de notre classe :

POJOUtilisateur1.java :

package utilisateur1;

public class POJOUtilisateur1 {

private long idUser;

private String nom;

private String prenom;

private String password;

private java.util.Date dateCM;

private int isSupprimable;

public void setIdUser (long _id)

{

idUser=_id;

}

public long getIdUser()

{

return idUser;

}

public void setNom (String _nom)

{

nom=_nom;

}

public String getNom()

{

return nom;

}

public void setPrenom (String _prenom)

{

prenom=_prenom;

}

public String getPrenom()

{

return prenom;

}

public void setPassword (String _password)

{

password=_password;

}

public String getPassword()

{

return password;

}

public void setDateCM (java.util.Date _date)

{

dateCM=_date;

}

public java.util.Date getDateCM()

{

return dateCM;

}

public void setIsSupprimable (int _isSup)

{

isSupprimable=_isSup;

}

public int getIsSupprimable()

{

return isSupprimable;

}

}

La compilation ne pose aucun souci par :

javac utilisateur1/*.java

III-C-3-b - Mettre à jour hibernate.properties

Il faut maintenant configurer le fichier hibernate.properties.
Nous allons faire une copie de ce fichier sous l'arborescence /home/pastjl/arheu_arheu/samples/ex1 qui correspond à mon espace de travail pour ce premier exemple.
Il nous faut déclarer un pool de connexion pour permettre à Hibernate de se connecter à la base. Hibernate en propose plusieurs, nous allons prendre le pool build-in propre à Hibernate, même s'il n'est pas performant.
Le pool de connexions choisi est paramétré comme indiqué ci-dessous dans l'extrait du fichier hibernate.properties :

## PostgreSQL

hibernate.dialect net.sf.hibernate.dialect.PostgreSQLDialect

hibernate.connection.driver_class org.postgresql.Driver

hibernate.connection.url jdbc:postgresql:essai

hibernate.connection.username pastjl

hibernate.connection.password

hibernate.connexion.pool_size 10

#hibernate.query.substitutions yes 'Y', no 'N'

III-C-3-c - Il faut écrire le fichier XML de mapping 0/R

Nous l'appellerons (et il faut impérativement respecter ce nommage pour permettre le chargement par le nom de classe => voir plus bas) POJOUtilisateur1.hbm.xml :

POJOUtilisateur1.hbm.xml

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>

<class name="utilisateur1.POJOUtilisateur1" table="utilisateurs">

<id name="idUser" type="long" unsaved-value="null" column ="iduser">

<generator class="sequence"/>

</id>

<property name="nom" type="string"/>

<property name="prenom" type="string"/>

<property name="password" type="string"/>

<property name="dateCM" type="timestamp" column="datecm"/>

<property name="isSupprimable" type="integer" column="issupprimable"/>

</class>

</hibernate-mapping>

Noter l'élément generator encapsulé dans l'élément id qui correspond à la primary key. Cet élément permet de générer de manière unique une clé primaire. L'attribut class associé définit le moyen de générer cette PK. Avec Postgres, Oracle, DB2 on peut utiliser des séquences. Voir la documentation de Référence Hibernate pour les autres classes de génération (§ 5.1.4.1. generator)

III-C-3-d - Client pour jouer avec POJOUtilisateur1

Il faut maintenant écrite le client, pour jouer avec notre classe POJO.

Pour cela, il faut initialiser un contexte, pour que Hibernate puisse retrouver toutes les informations. Il faut que le fichier hibernate.properties soit dans le classpath lors de l'exécution du programme.

Il faut d'autre part que Hibernate puisse charger le mapping O/R déclaré dans notre fichier POJOUtilisateur1.hbm.xml.

Il y a plusieurs façons de faire :

- dans la classe cliente, positionner ce fichier dans le contexte d'exécution (par le fichier ou la classe) ;

- créer un fichier hibernate.cfg.xml, ou le ou les fichiers de mapping sont décrits.

Dans ce second cas notre fichier , un extrait hibernate.cfg.xml aurait la tête suivante :

<?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>

<!-- a SessionFactory instance listed as /jndi/name -->

<session-factory ....>

....

<!-- mapping files -->

<mapping resource="POJOUtilisateur1.hbm.xml"/>

...

</session-factory>

</hibernate-configuration>

Pour notre premier exemple , nous utiliserons la première méthode, en configurant notre contexte dans la classe cliente.

Ci-dessous, le code de la classe cliente utilisée (identifié comme ClientTest Figure 2 plus haut) :

ClientEx1.java

package utilisateur1;

import net.sf.hibernate.*;

import net.sf.hibernate.cfg.*;

public class ClientEx1

{

public static void main(String [] argv) throws Exception, MappingException,

HibernateException

{

Transaction tx=null;

Session session=null;

try

{

Configuration cfg = new Configuration()

.addClass(utilisateur1.POJOUtilisateur1.class);

SessionFactory sessionFactory =   cfg.buildSessionFactory();

// ouverture de la session

session =sessionFactory.openSession();

// Creation d'un user

tx = session.beginTransaction();

POJOUtilisateur1 util1=new POJOUtilisateur1();

// on peuple l'objet util

// mais pas la clé primaire, qui nous sera retournée par session.save();

util1.setNom("Pasturel1");

util1.setPrenom("jl1");

util1.setPassword("pasturel1");

util1.setDateCM(new java.util.Date());

util1.setIsSupprimable(1);

// on recupere la primary key

Long id= (Long) session.save(util1);

tx.commit();

System.out.println("Id sauvegarde = "+id.toString());

}

catch (Exception e) {

if (tx!=null) tx.rollback();

throw e;

}

finally {

session.close();

}

}

}

J'ai mis ce client dans le même package que le bean POJOUtilisateur1.
Ce premier client créé un utilisateur dans la table utilisateur, en relançant plusieurs fois on crée des lignes identiques sauf pour la primary key évidemment.

Pour compiler, j'ai utilisé le script suivant qui initialise le classpath avec toutes les bibliothèques .jar livrées avec hibernate (c'est facile avec Unix, ce sera plus dur avec Windows => intégrer les classpath un par un).

Le script de compilation compilEx1.ksh :

compilEx1.ksh

#!/bin/bash

#Déclaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur1 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

javac -classpath $CLASSPATH utilisateur1/ClientEx1.java

Et le script de lancement lanceEx1.ksh très similaire :

#!/bin/bash

#Declaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur1 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

java -classpath $CLASSPATH utilisateur1.ClientEx1

Noter que le fichier principal est hibernate2.jar.

D'autre part pour que cela fonctionne, il faut ramener des fichiers de configuration de log4j et de gestion de cache sous l'arborescence /home/pastjl/arheu_arheu/samples/ex1 correspondant au répertoire de lancement. Ces fichiers se trouvent sous /opt/hibernate-2.1/src et ce sont : cache.ccf, oscache.properties,ehcache.xml, log4j.properties, treecache.xml. Hibernate utilise le classpath contextuel pour repérer ces fichiers.

Et après plusieurs lancements, la table utilisateurs a l'allure ci-dessous :

essai=> select * from utilisateurs;

iduser |    nom    | prenom | password  |           datecm           | issupprimable

--------+-----------+--------+-----------+----------------------------+---------------

1 | admin     | admin  | admin     | 2004-04-28 21:34:26.036093 |             0

24 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 21:53:54.309    |             1

25 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 21:54:22.961    |             1

26 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:03:03.054    |             1

27 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:12:16.629    |             1

28 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:21:10.92     |             1

(6lignes)

III-C-3-e - Analyse du code

L'analyse du code montre les opérations suivantes :

- initialiser une configuration avec le ou les fichiers de mapping ;

- créer, à partir de la configuration, une fabrique de sessions (nous verrons plus loin, l'utilisation de ThreadLocal, pour faire des sessions Thread safe) => SessionFactory ;

- créer et ouvrir la session par SessionFactory.open() ;

- démarrer une transaction sur la session par Session.beginTransaction() ;

- peupler les properties du POJOUtilisateur1 => séries de set<property> (sauf la pk, car automatique dans notre cas) ;

- commiter la transaction (ou rollbacker en cas d'échec) Transaction.commit() ;

- fermer la session Session.close().

Nota : pour ce premier exemple, vous n'avez besoin d'installer que Hibernate (TOMCAT et JSF ne sont pas encore nécessaires).

III-C-3-f - Jouons un peu plus avec notre POJOUtilisateur1

Nous modifions un peu notre client pour récupérer un utilisateur par son pk, modifier son password , supprimer un utilisateur.
On va modifier le password de l'utilisateur d'iduser 24 et supprimer l'utilisateur de iduser=28.

La classe Client_1Ex1.java :

Client_1Ex1.java

package utilisateur1;

import net.sf.hibernate.*;

import net.sf.hibernate.cfg.*;

public class Client_1Ex1

{

public static void main(String [] argv) throws Exception, MappingException,

HibernateException

{

Transaction tx=null;

Session session=null;

POJOUtilisateur1 util1=null;

try

{

Configuration cfg = new Configuration()

.addClass(utilisateur1.POJOUtilisateur1.class);

SessionFactory sessionFactory =   cfg.buildSessionFactory();

// ouverture de la session

session =sessionFactory.openSession();

// Modification d'un utilisateur

tx = session.beginTransaction();

// Récupérons l utilsateur de iduser = 24

util1= (POJOUtilisateur1) session.load(POJOUtilisateur1.class,new Long(24));

// Modifions son password

util1.setPassword("arheu123");

// Sauvons l'enregistrement

session.update(util1);

// Suppression de l utilisateur dont iduser passé en argument de la fonction main

long lg=Long.parseLong(argv[0]);

util1= (POJOUtilisateur1) session.load(POJOUtilisateur1.class,new Long(lg));

session.delete(util1);

tx.commit();

}

catch (Exception e) {

if (tx!=null) tx.rollback();

throw e;

}

finally {

session.close();

}

}

}

Le script de compilation rendu générique genCompilEx1.ksh :

#!/bin/bash

#Déclaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur1 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

javac -classpath $CLASSPATH utilisateur1/*.java

Le script de lancement rendu aussi plus générique genLanceEx1.ksh :

#!/bin/bash

#Déclaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur1 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

java -classpath $CLASSPATH $*

On lance :

genLanceEx1.ksh utilisateur1.Client_1Ex1 28

et on a le résultat ci-dessous :

Avant lancement

essai=> select * from utilisateurs;

iduser |    nom    | prenom | password  |           datecm           | issupprimable

--------+-----------+--------+-----------+----------------------------+---------------

1 | admin     | admin  | admin     | 2004-04-28 21:34:26.036093 |             0

24 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 21:53:54.309    |             1

25 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 21:54:22.961    |             1

26 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:03:03.054    |             1

27 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:12:16.629    |             1

28 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:21:10.92     |             1

29 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:49:18.387    |             1

30 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:51:08.809    |             1

(8lignes)

Après lancement

essai=> select * from utilisateurs;

iduser |    nom    | prenom | password  |           datecm           | issupprimable

--------+-----------+--------+-----------+----------------------------+---------------

1 | admin     | admin  | admin     | 2004-04-28 21:34:26.036093 |             0

25 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 21:54:22.961    |             1

26 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:03:03.054    |             1

27 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:12:16.629    |             1

29 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:49:18.387    |             1

30 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:51:08.809    |             1

24 | Pasturel1 | jl1    | arheu123  | 2004-05-07 21:53:54.309    |             1

(7 lignes)

III-C-3-g - Récapitulation des opérations (cas manuel/sans ide)

Une chose à bien retenir, Hibernate est Class-centric (ou plutôt Java-Centric) plutôt que database-centric. Pour bien comprendre Hibernate, il faut raisonner en Objet et pas en termes de SGBD relationnel.

Les opérations à faire sont :

- configurer le fichier hibernate.properties (une fois suffit, on peut ensuite reprendre le même par la suite) ;

- définir le diagramme de classes (dans notre premier exemple, une seule classe) ;

- coder la classe POJO (éventuellement en y intégrant des tags Xdoclet si vous maitrisez) ;

- générer la table ou les tables => dans notre cas une seule table (nous verrons plus tard , que Hibernate propose trois stratégies de création de tables en cas d'héritage entre classes et des outils permettant de créer la base ainsi qu'un IDE Hibern8IDE) ;

- créer ou générer le/les fichiers de mapping O/R <classe>.hbm.xml ;

- créer le client pour utiliser le POJO.

III-C-3-h - Un peu d'information sur l'aspect non threadsafe de la session

Une application en général doit gérer une multiplicité de connexions et de transactions, il faut donc assurer que pour chaque session que les variables utilisées ne soient pas accessibles depuis d'autres sessions. En initialisant des sessions comme des ThreadLocal, les instances des variables sont dédiées au Thread en cours. Hibernate fournit une classe utilitaire pour gérer les sessions en ThreadLocal. La classe est recopiée telle quelle du document de référence de Hibernate :

import net.sf.hibernate.*;

import net.sf.hibernate.cfg.*;

public class HibernateUtil {

private static final SessionFactory sessionFactory;

static {

try {

// Create the SessionFactory

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

} catch (HibernateException ex) {

throw new RuntimeException("Configuration problem : " + ex.getMessage(), ex);

}

}

public static final ThreadLocal session = new ThreadLocal();

public static Session currentSession() throws HibernateException {

Session s = (Session) session.get();

// Open a new Session, if this Thread has none yet

if (s == null) {

s = sessionFactory.openSession();

session.set(s);

}

return s;

}

public static void closeSession() throws HibernateException {

Session s = (Session) session.get();

session.set(null);

if (s != null)

s.close();

}

}

et un extrait (de la documentation de référence Hibernate) pour son utilisation :

Session session = HibernateUtil.currentSession();

Transaction tx= session.beginTransaction();

Cat princess = new Cat();

princess.setName("Princess");

princess.setSex('F');

princess.setWeight(7.4f);

session.save(princess);

tx.commit();

HibernateUtil.closeSession();

Mais à manipuler avec précaution, après avoir bien compris la nécessité ou pas d'utiliser le mécanisme ThreadLocal pour rendre les sessions thread-safe.

III-C-4 - Un exemple un peu plus complexe

Sous la même architecture que l'exemple précédent, nous allons analyser ce que peut faire Hibernate en termes de mapping :

- dans le cas d'un héritage entre classes ;

- dans le cas d'association one-to-many ;

- dans le cas d'association many-to-many.

On trouvera ci-dessous le diagramme de classe, à partir duquel nous allons construire notre fichier de mapping O/R Hibernate. Ce diagramme de classe a été réalisé à l'aide de l'outil UML PoséidonUML CE. L'intérêt de ce type d'outil est qu'il est capable de générer les modèles au format xmi, qui sont utilisables par la suite à travers l'outil AndroMDA (voir documentation de référence Hibernate).

Figure : 3

PoséidonUML permet de générer les setter et getter à la création graphique à la "souris" et permet la génération du code source qui nous sert de squelette pour créer les POJO à faire persister.

On peut noter une association polymorphique entre Personne et Utilisateurs2 qui va nous amener à discuter des stratégies de création des tables dans la database.

III-C-4-a - Rappel sur les Collections en Java et utilisation dans Hibernate

Les principaux Objets Java utilisés pour assurer les relations entre Classes dans Hibernate sont décrits dessous. Il conviendra de choisir l'objet adapté au contexte de l'application :

- Collection : racine des interfaces Set, SortedSet, List ;

- Set : une collection qui ne contient aucun élément dupliqué ;

- SortedSet : un set dont l'iterator traversera l'ensemble dans l'ordre croissant naturel de ses éléments ou à partir d'un comparateur fourni à la création du SortedSet ;

- List : une collection ordonnée (également connue sous le nom de séquence). L'utilisateur de cette interface a le contrôle précis où chaque élément est inséré dans la liste. L'utilisateur peut accéder à des éléments par leur index (position dans la liste), et rechercher des éléments dans la liste ;

- Map : un objet qui mappe (relie) des clefs à des valeurs. Une map ne peut pas contenir des clefs doubles ; chaque clef identifie une valeur unique ;

- SortedMap : une map organisée dans l'ordre croissant naturel de la clef, ou à partir d'un comparateur fourni à la création du SortedMap.

Toute classe implémentant une ou plusieurs de ces interfaces est candidate pour décrire les relations multiples entre classes.
Tout tableau peut aussi décrire aussi une relation multiple entre classes.
L'aspect bag (panier : où l'on peut avoir des valeurs dupliquées) peut se traiter à travers des objets implémentant l'interface Collection ou List ou bien un tableau.

IMPORTANT : Le type de la propriété à persister dans la classe doit être du type de l'interface et pas de la classe implémentant l'interface : soit la liste exhaustive avec leur interface dérivée (Set, Map, List) et tableau mais pas par exemple (TreeSet, HashSet, HashMap, ArrayList…)

On écrira par exemple :

Set mesFils = new HashSet();

mais pas :

HashSet mesFils = new HashSet(); // erreur

III-C-4-b - Stratégie de création des tables de type "table per class hierarchy"

On positionne la classe et ses sous-classes dans une même table. L'inconvénient, c'est que l'on ne peut pas mettre de clause « not-null » dans les propriétés des sous-classes. Nous allons procéder comme suit :

- écrire à la main le fichier de mapping pour chaque classe ;

- créer un fichier de mapping général qui « inclura » tous les fichiers mapping réalisés ci-dessus ;

- utiliser l'outil SchemaExport, intégré au package Hibernate, qui à partir des classes POJO générées et le fichier de mapping général permet de générer la création des tables dans la database (on peut générer réellement les tables dans la database ou récupérer le script de création sous forme texte)

personne.hbm.xml

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 2.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<!-- Strategie table-per-class hierarchy mapping -->

<hibernate-mapping package="utilisateur2">

<class name="Personne" table="PERSONNES" discriminator-value="P">

<id name="idPers" column="idPers" type="long">

<generator class="sequence"/>

</id>

<discriminator column="sousclasse" type="character"/>

<property name="nom" column="nom" type="string"/>

<property name="prenom" column="prenom" type="string"/>

<set name="listutilisateurs2">

<key column="idPers"/>

<one-to-many class="utilisateur2.Utilisateur2" />

</set>

<subclass name="Employe" discriminator-value="E">

<property name="codeInterne" column="codeInterne" type="string"/>

</subclass>

<subclass name="Externe" discriminator-value="X">

<property name="societe" column="societe" type="string"/>

</subclass>

</class>

</hibernate-mapping>

Les objets Personne, Externe et Employe sont stockés dans la même table, comme le montrent les scripts de création générés par SchemaExport (nous verrons plus loin son utilisation) :

create table PERSONNES (

idPers INT8 not null,

sousclasse CHAR(1) not null,

nom VARCHAR(255),

prenom VARCHAR(255),

codeInterne VARCHAR(255),

societe VARCHAR(255),

primary key (idPers)

)

La colonne sous-classe accueille le discriminator qui est P pour Personne, E pour Employe et X pour Externe.
La limitation importante c'est que les colonnes des propriétés des sous-classes ne peuvent pas supporter la contrainte not-null.

III-C-4-c - Stratégie de création des tables de type « table per sub-class hierarchy »

Le fichier de mapping est donné ci-dessous pour cette stratégie (e tag clé est joined-subclass) :

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 2.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<!-- Strategie table-per-class hierarchy mapping -->

<hibernate-mapping package="utilisateur2">

<class name="Personne" table="PERSONNES" >

<id name="idPers" column="idPers" type="long">

<generator class="sequence"/>

</id>

<property name="nom" column="nom" type="string"/>

<property name="prenom" column="prenom" type="string"/>

<set name="listutilisateurs2">

<key column="idPers"/>

<one-to-many class="utilisateur2.Utilisateur2" />

</set>

<joined-subclass name="Employe" table="EMPLOYES">

<key column="idPers"/>

<property name="codeInterne" column="codeInterne" type="string"/>

</joined-subclass>

<joined-subclass name="Externe" table="EXTERNES">

<key column="idPers"/>

<property name="societe" column="societe" type="string"/>

</joined-subclass>

</class>

</hibernate-mapping>

Il y a génération de trois tables, la classe mère et ses deux sous-classes à l'aide de SchemaExport :

create table EMPLOYES (

idPers INT8 not null,

codeInterne VARCHAR(255),

primary key (idPers)

)

...

create table EXTERNES (

idPers INT8 not null,

societe VARCHAR(255),

primary key (idPers)

)

create table PERSONNES (

idPers INT8 not null,

nom VARCHAR(255),

prenom VARCHAR(255),

primary key (idPers)

)

...

alter table EMPLOYES add constraint FK75C8D6BCB8D3EEB1 foreign key (idPers) references PERSONNES

...

alter table EXTERNES add constraint FKC21F544EB8D3EEB1 foreign key (idPers) references PERSONNES

...

Noter la génération des foreign key entre les tables filles et la table mère dans une relation de type one-to-one.

III-C-4-d - Stratégie de création des tables de type " table per concrete class "

Il s'agirait dans notre cas de créer deux tables correspondant aux classes Externe et Employe (en effet une personne peut être considérée soit comme un externe soit comme un employé).

Nous sommes hélas dans le cas d'une relation polymorphique one-to-many non-supportée par Hibernate (voir Document de Référence , Chapitre 8.2 Limitations Table 8.1. Features of inheritance mappings). Il faudrait créer deux relations one-to-many entre Employe et Utilisateur2 et entre Externe et Utilisateur2. Je laisse le soin au lecteur de se livrer à cet exercice. ;-) . Mais cela ne représente aucune difficulté majeure.

Remarque importante :
Quelle que soit la stratégie choisie, elle n'a aucun impact sur le code Java des POJO, (Hibernate est java-centric et pas database-Centric).
Hibernate Query Language (HQL) adresse les objets et pas les tables, ce code HQL est aussi indépendant de la stratégie d'implémentation des tables.
La stratégie influence les requêtes en pur SQL, qu'il est possible de lancer à travers Hibernate. Le bon choix étant bien entendu HQL quand cela est possible.

III-C-4-e - Les POJO utilisés dans notre exemple

Pour notre exemple, je choisis la stratégie d'une table pour la hiérarchie de classe. Je n'ai pas trouvé de recommandation dans la documentation pour tel ou tel cas d'utilisation. Il faut vérifier le tableau des limitations par rapport à son usage. Il faut voir cela par rapport aux possibilités de performances de la base. Voir avec votre DBA.

La classe Personne et ses deux classes dérivées Externe et Employe :

Personne.java

package utilisateur2;

import java.lang.Long;

import java.lang.String;

import java.util.*;

public class Personne {

private Long idPers;

private String nom;

private String prenom;

// associations

private Set  utilisateur2s; // of type Utilisateur2

// operations

public void  setIdPers(Long _id) {

idPers=_id;

}

public Long getIdPers() {

return idPers;

}

public Set getUtilisateur2s()

{

return utilisateur2s;

}

public void setUtilisateur2s(Set _util)

{

utilisateur2s=_util;

}

public String getNom() {

return nom;

} // end getNom

public void setNom(String _nom) {

nom = _nom;

} // end setNom

public String getPrenom() {

return prenom;

} // end getPrenom

public void setPrenom(String _prenom) {

prenom = _prenom;

} // end setPrenom

} // end Personne

Externe.java

package utilisateur2;

import java.lang.String;

import java.util.*;

public class Externe extends Personne {

private String societe;

public String getSociete() {

return societe;

} // end getSociete

public void setSociete(String _societe) {

societe = _societe;

} // end setSociete

} // end Externe

À noter simplement l'héritage de Personne (voir le fichier hbm.xml correspondant plus loin).

Employe.java

package utilisateur2;

import java.lang.String;

import java.util.*;

public class Employe extends Personne {

private String codeInterne;

public String getCodeInterne() {

return codeInterne;

} // end getCodeInterne

public void setCodeInterne(String _codeInterne) {

codeInterne = _codeInterne;

} // end setCodeInterne

} // end Employe

La classe Utilisateur2

Utilisateur2.java

package utilisateur2;

import java.lang.Long;

import java.lang.String;

import java.util.*;

import java.util.Date;

public class Utilisateur2 {

// attributes

private Long idUser;

private String login;

private String password;

private Date dateCM;

private Integer isSupprimable;

// associations

private Personne pers;

public Set  connexions ; // of type Connexion

public Set roles ;  // of type Role

// operations

public Set getConnexions()

{

return connexions;

}

public void setConnexions(Set _cons)

{

connexions=_cons;

}

public Set getRoles()

{

return roles;

}

public void setRoles(Set _roles)

{

roles=_roles;

}

public String getLogin() {

return login;

} // end getLogin

public void setLogin(String _login) {

login = _login;

} // end setLogin

public Long getIdUser() {

return idUser;

}

public void setIdUser(Long _id){

idUser= _id;

}

public Personne getPers() {

return pers;

}

public void setPers(Personne _pers){

pers=_pers;

}

public String getPassword() {

return password;

} // end getPassword

public void setPassword(String _password) {

password = _password;

} // end setPassword

public Date getDateCM() {

return dateCM;

} // end getDateCM

public void setDateCM(Date _dateCM) {

dateCM = _dateCM;

} // end setDateCM

public Integer getIsSupprimable() {

return isSupprimable;

} // end getIsSupprimable

public void setIsSupprimable(Integer _isSupprimable) {

isSupprimable = _isSupprimable;

} // end setIsSupprimable

} // end Utilisateur2

Règle : si l'utilisateur est supprimé, on va supprimer les connexions associées ainsi que la ligne de la table d'association avec le rôle. Voir le fichier hbm.xml correspondant plus loin et le rôle particulier de l'attribut cascade dans les fichiers de mapping Hibernate.

Note importante :
les associations (relations) entre les objets persistants doivent se faire à l'aide d'attribut qui sont :

- soit des objets persistants ;

- soit des collections d'objets persistants.

Si on avait voulu décrire la relation Personne -> Utilisateur2 par l'attribut idPers de type Long dans Utilisateur2 (à la place de l'attribut pers de type Personne), nous aurions eu à l'exécution l'erreur suivante dans la console Hibernate :

MappingException : no persister for : java.lang.Long

La classe Connexion

Connexion.java

package utilisateur2;

import java.lang.Long;

import java.util.*;

import java.util.Date;

public class Connexion {

// attributes

private Long idCnx;

private Date dateDeb;

private Date dateFin;

// association

private Utilisateur2 util;

// operations

public void setIdCnx(Long _id) {

idCnx=_id;

}

public Long getIdCnx() {

return idCnx;

}

public void setUtil(Utilisateur2 _id) {

util=_id;

}

public Utilisateur2  getUtil() {

return util;

}

public Date getDateFin() {

return dateFin;

} // end getDateFin

public void setDateFin(Date _dateFin) {

dateFin = _dateFin;

} // end setDateFin

public Date getDateDeb() {

return dateDeb;

} // end getDateDeb

public void setDateDeb(Date _dateDeb) {

dateDeb = _dateDeb;

} // end setDateDeb

} // end Connexion

La classe Role

Role.java

package utilisateur2;

import java.lang.String;

import java.util.*;

public class Role {

// attributes

private long idRole;

private String nom;

// associations

private Set utilisateur2s ;

// operations

public Set getUtilisateur2s()

{

return utilisateur2s;

}

public void setUtilisateur2s(Set _util)

{

utilisateur2s=_util;

}

public long getIdRole() {

return idRole;

} // end getIdRole

public void setIdRole(long _idRole) {

idRole = _idRole;

} // end setIdRole

public void setNom(String _nom) {

nom=_nom;

}

public String getNom() {

return nom;

}

} // end Role

III-C-4-f - Les fichiers de mapping .hbm.xml correspondants.

Le fichier de mapping Personne.hbm.xml :

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 2.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<!-- Strategie table-per-class hierarchy mapping -->

<hibernate-mapping package="utilisateur2">

<class name="Personne" table="PERSONNES" discriminator-value="P">

<id name="idPers" column="idPers" type="long">

<generator class="sequence"/>

</id>

<discriminator column="sousclasse" type="character"/>

<property name="nom" column="nom" type="string"/>

<property name="prenom" column="prenom" type="string"/>

<set name="utilisateur2s" inverse="true" cascade="all-delete-orphan" >

<key column="pers"/>

<one-to-many class="utilisateur2.Utilisateur2" />

</set>

<subclass name="Employe" discriminator-value="E">

<property name="codeInterne" column="codeInterne" type="string"/>

</subclass>

<subclass name="Externe" discriminator-value="X">

<property name="societe" column="societe" type="string"/>

</subclass>

</class>

</hibernate-mapping>

Noter :
les discriminateurs P, E et X permettent de déterminer la classe Personne et ses sous-classes Externe et Employe.
La clause cascade=« all-delete-orphan » pour détruire les fils Utilisateur2 en cas de destruction de la Personne.
Le fichier de mapping Utilisateur2.hbm.xml :

<?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 package="utilisateur2">

<class name="Utilisateur2" table="UTILISATEUR2" >

<id name="idUser" column="idUser" type="long">

<generator class="sequence" />

</id>

<property name="login" column="login" type="string"/>

<property name="password" column="password" type="string"/>

<property name="dateCM" column="datecm" type="timestamp"/>

<property name="isSupprimable" column="issuprimable" type="int"/>

<many-to-one name="pers" class="utilisateur2.Personne" not-null="true"   />

<set name="connexions" inverse="true" cascade="all-delete-orphan" >

<key column="util" />

<one-to-many class="utilisateur2.Connexion"/>

</set>

<set  name="roles" table="UTILISATEUR2_ROLES"     >

<key column="idUser" />

<many-to-many  class="utilisateur2.Role" column="idRole"   />

</set>

</class>

</hibernate-mapping>

Noter la description de deux associations :
one-to-many avec une règle de cascade, many-to-many avec aussi une règle de cascade.
Le fichier de mapping Connexion.hbm.xml :

<?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 package="utilisateur2">

<class name="Connexion" table="CONNEXIONS" >

<id name="idCnx" column="idCnx" type="long">

<generator class="sequence" />

</id>

<property name="dateDeb" column="dateDeb" type="date"/>

<property name="dateFin" column="dateFin" type="date"/>

<many-to-one name="util" class="utilisateur2.Utilisateur2" column="util"  not-null="true"/>

</class>

</hibernate-mapping>

Le fichier de mappind Role.hbm.xml :

<?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 package="utilisateur2">

<class name="Role" table="ROLES" >

<id name="idRole" column="idRole" type="long">

<generator class="sequence" />

</id>

<property name="nom" column="nom" type="string"/>

<set  name="utilisateur2s" table="UTILISATEUR2_ROLES" inverse="true"   >

<key column="idRole" />

<many-to-many  class="utilisateur2.Utilisateur2" column="idUser" />

</set>

</class>

</hibernate-mapping>

Pour cet exemple ex2, les fichiers shell, classes et hbm.xml sont localisés sous les répertoires décrits dans le tableau suivant :

Répertoires

samples

ex2

utilisateurs2

Fichiers

*.ksh

*.java *.class *.hbm.xml

III-C-4-g - Génération du script de création de la base (outil SchemaExport)

Hibernate fournit en standard un outil permettant de générer la base. On peut :

- directement créer la base (y compris au lancement de l'application => voir document de référence Hibernate , mais on détruit et on recrée à chaque lancement) ;

- récupérer le script de création de base et le lancer après.

J'ai choisi la deuxième solution.

Pour notre exemple ex2, ci-dessous le script de création de notre base :

#!/bin/bash

#Déclaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur2 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

java -cp $CLASSPATH  net.sf.hibernate.tool.hbm2ddl.SchemaExport  --text --format

--output=ex2bis.ddl --delimeter=x $*

Et on lance pour notre exemple avec la commande suivante (sur une seule ligne de commande) :

./genSchema.ksh utilisateur2/Connexion.hbm.xml utilisateur2/Personne.hbm.xml

utilisateur2/Utilisateur2.hbm.xml utilisateur2/Role.hbm.xml

Le fichier de création de base est le fichier ex2bis.ddl donné ci-dessous :

alter table UTILISATEUR2_ROLES drop constraint FK9C293F8DB8D66786

alter table UTILISATEUR2_ROLES drop constraint FK9C293F8DB8D4FC31

alter table UTILISATEUR2 drop constraint FK4FCF252F3472F6

alter table CONNEXIONS drop constraint FKEFD16ECA36F002

drop table ROLES

drop table UTILISATEUR2_ROLES

drop table UTILISATEUR2

drop table CONNEXIONS

drop table PERSONNES

drop sequence hibernate_sequence

create table ROLES (

idRole INT8 not null,

nom VARCHAR(255),

primary key (idRole)

)

create table UTILISATEUR2_ROLES (

idUser INT8 not null,

idRole INT8 not null,

primary key (idRole, idUser)

)

create table UTILISATEUR2 (

idUser INT8 not null,

login VARCHAR(255),

password VARCHAR(255),

datecm TIMESTAMP,

issuprimable INT4,

pers INT8 not null,

primary key (idUser)

)

create table CONNEXIONS (

idCnx INT8 not null,

dateDeb DATE,

dateFin DATE,

util INT8 not null,

primary key (idCnx)

)

create table PERSONNES (

idPers INT8 not null,

sousclasse CHAR(1) not null,

nom VARCHAR(255),

prenom VARCHAR(255),

codeInterne VARCHAR(255),

societe VARCHAR(255),

primary key (idPers)

)

alter table UTILISATEUR2_ROLES add constraint FK9C293F8DB8D66786 foreign key (idUser) references UTILISATEUR2

alter table UTILISATEUR2_ROLES add constraint FK9C293F8DB8D4FC31 foreign key (idRole) references ROLES

alter table UTILISATEUR2 add constraint FK4FCF252F3472F6 foreign key (pers) references PERSONNES

alter table CONNEXIONS add constraint FKEFD16ECA36F002 foreign key (util) references UTILISATEUR2

create sequence hibernate_sequence

On notera que les ordres ddl ne se terminent pas par un « ; » (je n'ai pas réussi à le faire générer, je pensais que c'était lié au paramètre delimiter mais je n'y suis pas arrivé). Je pense que la génération sans « ; » correspond à la syntaxe par accès JDBC à la base. Si on veut utiliser ce script pour le lancer à la main depuis une connexion database, il faudra rajouter un ; à la fin de chaque ordre ddl.

III-C-4-h - Création de la database à l'ide du script ddl généré

Nous lancerons ce script de manière manuelle. Après rajout des ; ce script a la forme ci-dessous :

ex2bis.ddl

alter table UTILISATEUR2_ROLES drop constraint FK9C293F8DB8D66786;

alter table UTILISATEUR2_ROLES drop constraint FK9C293F8DB8D4FC31;

alter table UTILISATEUR2 drop constraint FK4FCF252F3472F6;

alter table CONNEXIONS drop constraint FKEFD16ECA36F002;

drop table ROLES;

drop table UTILISATEUR2_ROLES;

drop table UTILISATEUR2;

drop table CONNEXIONS;

drop table PERSONNES;

drop sequence hibernate_sequence;

create table ROLES (

idRole INT8 not null,

nom VARCHAR(255),

primary key (idRole)

);

create table UTILISATEUR2_ROLES (

idUser INT8 not null,

idRole INT8 not null,

primary key (idRole, idUser)

);

create table UTILISATEUR2 (

idUser INT8 not null,

login VARCHAR(255),

password VARCHAR(255),

datecm TIMESTAMP,

issuprimable INT4,

pers INT8 not null,

primary key (idUser)

);

create table CONNEXIONS (

idCnx INT8 not null,

dateDeb DATE,

dateFin DATE,

util INT8 not null,

primary key (idCnx)

);

create table PERSONNES (

idPers INT8 not null,

sousclasse CHAR(1) not null,

nom VARCHAR(255),

prenom VARCHAR(255),

codeInterne VARCHAR(255),

societe VARCHAR(255),

primary key (idPers)

);

alter table UTILISATEUR2_ROLES add constraint FK9C293F8DB8D66786 foreign key (idUser) references UTILISATEUR2;

alter table UTILISATEUR2_ROLES add constraint FK9C293F8DB8D4FC31 foreign key (idRole) references ROLES;

alter table UTILISATEUR2 add constraint FK4FCF252F3472F6 foreign key (pers) references PERSONNES;

alter table CONNEXIONS add constraint FKEFD16ECA36F002 foreign key (util) references UTILISATEUR2;

create sequence hibernate_sequence;

Lancer la base posgres, par les commandes postgres de démarrage du postmaster.
Sous votre user unix habituel (et non celui de postgres), se positionner sous l'arborescence ex2 et se connecter à la base essai :

psql essai

et lancer le script :

\i ex2bis.ddl

On peut visualiser les tables créées ainsi que leur structure. Ci-dessous un extrait de cette analyse.

essai=> \dt

Liste des relations

Schéma |        Nom         | Type  | Propriétaire

--------+--------------------+-------+--------------

public | auctionitem        | table | pastjl

public | auctionuser        | table | pastjl

public | bid                | table | pastjl

public | connexions         | table | pastjl

public | personnes          | table | pastjl

public | roles              | table | pastjl

public | utilisateur2       | table | pastjl

public | utilisateur2_roles | table | pastjl

public | utilisateurs       | table | pastjl

(9 lignes)

essai=> \d utilisateur2

Table «public.utilisateur2»

Colonne    |            Type             | Modificateurs

--------------+-----------------------------+---------------

iduser       | bigint                      | not null

login        | character varying(255)      |

password     | character varying(255)      |

datecm       | timestamp without time zone |

issuprimable | integer                     |

pers         | bigint                      | not null

Index :

«utilisateur2_pkey» clé primaire, btree (iduser)

Contraintes de clés secondaires :

«fk4fcf252f3472f6» FOREIGN KEY (pers) REFERENCES personnes(idpers)

III-C-4-i - Écriture des classes (Facade et Client) d'utilisation des POJO

Les fonctions que nous allons implémenter dans cette classe, qui servira ensuite de Session Façade, sont les suivantes :

- création et suppression de personnes ;

- création et suppression de rôles ;

- création et suppression d'utilisateurs (avec un lien sur personne (gestion des orphelins, rôles, connexions) ;

- création et suppression de connexions (gestion des orphelins) ;

- modification Utilisateur (changement mot de passe).

Le schéma de la figure 2 est un peu modifié pour donner le schéma ci-dessous :

Figure 3 : exemple ex2

Pour la suite , je vais progressivement (même si cela devient verbeux) enrichir :

- la classe java client test : ClientEx2.java ;

- la classe java Session Façade : FaçadeEx2.java ;

- les tables de la databases (conséquences en fait des lancements successifs du client test).

Le bundle des sources des exemples n'intégrera que la version complète des deux classes citées ci-dessus.

III-C-4-j - Premier test basique : création de Personnes à travers les sous-classes Externe et Employe

La classe ClientEx2.java :
Création de 8 « Personne » : 4 « Employe » et 4 « Externe »

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2 {

public static void main (String argv[]) throws HibernateException {

FacadeEx2 facadeEx2 = new FacadeEx2();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

// Creation de 4 Employe

facadeEx2.createEmploye("PASTUREL0","JEAN-LOUIS0","PAST0000");

facadeEx2.createEmploye("PASTUREL1","JEAN-LOUIS1","PAST0001");

facadeEx2.createEmploye("PASTUREL2","JEAN-LOUIS2","PAST0002");

facadeEx2.createEmploye("PASTUREL3","JEAN-LOUIS3","PAST0003");

// Creation de 4 Externe

facadeEx2.createExterne("EXTERNE0","JEAN-LOUIS0","SOCI0000");

facadeEx2.createExterne("EXTERNE1","JEAN-LOUIS1","SOCI0001");

facadeEx2.createExterne("EXTERNE2","JEAN-LOUIS2","SOCI0002");

facadeEx2.createExterne("EXTERNE3","JEAN-LOUIS3","SOCI0003");

}

}

La classe FacadeEx2 .java correspondante :

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2 {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

public Employe createEmploye(String _nom, String _prenom,String _codeInterne)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Employe emp=new Employe();

emp.setNom(_nom);

emp.setPrenom(_prenom);

emp.setUtilisateur2s(new HashSet());

emp.setCodeInterne(_codeInterne);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(emp);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return emp;

}

public Externe createExterne(String _nom, String _prenom,String _societe)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Externe ext=new Externe();

ext.setNom(_nom);

ext.setPrenom(_prenom);

ext.setUtilisateur2s(new HashSet());

ext.setSociete(_societe);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(ext);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return ext;

}

}

Après compilation des classes, le lancement s'effectue à l'aide du script lanceEx2.ksh suivant :

#!/bin/bash

#Déclaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur2 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

java -classpath $CLASSPATH utilisateur2.ClientEx2

./lanceEX2.ksh

et le résultat dans la database :

essai=> select * from PERSONNES;

idpers | sousclasse |    nom    |   prenom    | codeinterne | societe

--------+------------+-----------+-------------+-------------+----------

1 | E          | PASTUREL0 | JEAN-LOUIS0 | PAST0000    |

2 | E          | PASTUREL1 | JEAN-LOUIS1 | PAST0001    |

3 | E          | PASTUREL2 | JEAN-LOUIS2 | PAST0002    |

4 | E          | PASTUREL3 | JEAN-LOUIS3 | PAST0003    |

5 | X          | EXTERNE0  | JEAN-LOUIS0 |             | SOCI0000

6 | X          | EXTERNE1  | JEAN-LOUIS1 |             | SOCI0001

7 | X          | EXTERNE2  | JEAN-LOUIS2 |             | SOCI0002

8 | X          | EXTERNE3  | JEAN-LOUIS3 |             | SOCI0003

(8 lignes)

III-C-4-k - Création de Users

On complète les classes ci-dessus. Avant de relancer le client test, on rejouera le script de création de la database qui la détruit et la recrée vide.
Comme nous avons une FK côté Utilisateur2 pointant vers personnes, nous allons affecter tous les utilisateurs à la personne dont le idPers=3, puis nous modifierons certains Utilisateurs pour les affecter à d'autres personnes.

La classe ClientEx2.java :

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2 {

public static void main (String argv[]) throws HibernateException {

Employe emp1;

Externe ext1;

Utilisateur2 user1, user2,user3,user4;

FacadeEx2 facadeEx2 = new FacadeEx2();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

// Creation de 4 Employe

emp1=facadeEx2.createEmploye("PASTUREL0","JEAN-LOUIS0","PAST0000");

facadeEx2.createEmploye("PASTUREL1","JEAN-LOUIS1","PAST0001");

facadeEx2.createEmploye("PASTUREL2","JEAN-LOUIS2","PAST0002");

facadeEx2.createEmploye("PASTUREL3","JEAN-LOUIS3","PAST0003");

// Creation de 4 Externe

ext1=facadeEx2.createExterne("EXTERNE0","JEAN-LOUIS0","SOCI0000");

facadeEx2.createExterne("EXTERNE1","JEAN-LOUIS1","SOCI0001");

facadeEx2.createExterne("EXTERNE2","JEAN-LOUIS2","SOCI0002");

facadeEx2.createExterne("EXTERNE3","JEAN-LOUIS3","SOCI0003");

// Creation de User pour Employe

user1= facadeEx2.createUtilisateur2("E_USER0_0","E_USER0_0",(int) 1);

user2=facadeEx2.createUtilisateur2("E_USER0_1","E_USER0_1",(int)1);

facadeEx2.createUtilisateur2("E_USER1_0","E_USER1_0",(int)1);

facadeEx2.createUtilisateur2("E_USER1_1","E_USER1_1",(int)1);

facadeEx2.createUtilisateur2("E_USER2_0","E_USER2_0",(int)1);

facadeEx2.createUtilisateur2("E_USER2_1","E_USER2_1",(int)1);

facadeEx2.createUtilisateur2("E_USER3_0","E_USER3_0",(int)1);

facadeEx2.createUtilisateur2("E_USER3_1","E_USER3_1",(int)1);

// Creation de User pour Externe

user3=facadeEx2.createUtilisateur2("X_USER0_0","X_USER0_0",(int)1);

user4=facadeEx2.createUtilisateur2("X_USER0_1","X_USER0_1",(int)1);

facadeEx2.createUtilisateur2("X_USER1_0","X_USER1_0",(int)1);

facadeEx2.createUtilisateur2("X_USER1_1","X_USER1_1",(int)1);

facadeEx2.createUtilisateur2("X_USER2_0","X_USER2_0",(int)1);

facadeEx2.createUtilisateur2("X_USER2_1","X_USER2_1",(int)1);

facadeEx2.createUtilisateur2("X_USER3_0","X_USER3_0",(int)1);

facadeEx2.createUtilisateur2("X_USER3_1","X_USER3_1",(int)1);

// Associer le user "E_USER_0_0" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user1.getIdUser());

// Associer le user "E_USER_0_1" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user2.getIdUser());

//Associer  le user "X_USER_0_0" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user3.getIdUser());

//Associer  le user "X_USER_0_1" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user4.getIdUser());

}

}

La classe FacadeEx2 .java correspondante :

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2 {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

public Employe createEmploye(String _nom, String _prenom,String _codeInterne)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Employe emp=new Employe();

emp.setNom(_nom);

emp.setPrenom(_prenom);

emp.setUtilisateur2s(new HashSet());

emp.setCodeInterne(_codeInterne);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(emp);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return emp;

}

public Externe createExterne(String _nom, String _prenom,String _societe)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Externe ext=new Externe();

ext.setNom(_nom);

ext.setPrenom(_prenom);

ext.setUtilisateur2s(new HashSet());

ext.setSociete(_societe);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(ext);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return ext;

}

public Utilisateur2 createUtilisateur2(String _login, String _password,int _issupprimable)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Utilisateur2 user=new Utilisateur2();

Personne pers=new Personne();

user.setLogin(_login);

user.setPassword(_password);

user.setIsSupprimable(new Integer(_issupprimable));

user.setDateCM(new java.util.Date());

//user.setIdPers(new Long(0));

user.setConnexions(new HashSet());

user.setRoles(new HashSet());

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

// pour respecter la clause non-null sur la Foreign-key pointant sur personne on

// affecte par defaut la personne 3

Query q2 = session.createQuery("from Personne where idPers = 3");

pers= (Personne) q2.list().get(0);

user.setPers(pers);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return user;

}

public boolean assocPersToUser(Long _idPers, Long _idUser) throws HibernateException {

boolean retour=true;

Utilisateur2 user =new Utilisateur2();

Personne pers = new Personne();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Query q1=session.createQuery("from Utilisateur2 where idUser = :idUser");

Query q2 = session.createQuery("from Personne where idPers = :idPers");

q1.setParameter("idUser", _idUser);

q2.setParameter("idPers", _idPers);

user= (Utilisateur2) q1.list().get(0);

System.out.println("Login = "+user.getLogin());

pers= (Personne) q2.list().get(0);

System.out.println ("Nom de la personne ="+pers.getNom()+" ;idPers = " +pers.getIdPers());

user.setPers((Personne) pers);

//user.setIdPers(emp.getIdPers());

((Set)((Personne)pers).getUtilisateur2s()).add((Utilisateur2) user);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

retour = false;

throw he;

}

finally {

session.close();

}

return retour;

}

}

Après compilation des classes et recréation de la base (relance de ex2bis.ddl connecté à la base), relance par

./lanceEX2.ksh

Et on a les résultats suivant dans la database :

essai=> select * from UTILISATEUR2 order by 1;

iduser |   login   | password  |         datecm          | issuprimable | pers

--------+-----------+-----------+-------------------------+--------------+------

9 | E_USER0_0 | E_USER0_0 | 2004-05-20 19:02:52.46  |            1 |    1

10 | E_USER0_1 | E_USER0_1 | 2004-05-20 19:02:52.584 |            1 |    1

11 | E_USER1_0 | E_USER1_0 | 2004-05-20 19:02:52.638 |            1 |    3

12 | E_USER1_1 | E_USER1_1 | 2004-05-20 19:02:52.653 |            1 |    3

13 | E_USER2_0 | E_USER2_0 | 2004-05-20 19:02:52.685 |            1 |    3

14 | E_USER2_1 | E_USER2_1 | 2004-05-20 19:02:52.704 |            1 |    3

15 | E_USER3_0 | E_USER3_0 | 2004-05-20 19:02:52.727 |            1 |    3

16 | E_USER3_1 | E_USER3_1 | 2004-05-20 19:02:52.794 |            1 |    3

17 | X_USER0_0 | X_USER0_0 | 2004-05-20 19:02:52.848 |            1 |    5

18 | X_USER0_1 | X_USER0_1 | 2004-05-20 19:02:52.881 |            1 |    5

19 | X_USER1_0 | X_USER1_0 | 2004-05-20 19:02:52.939 |            1 |    3

20 | X_USER1_1 | X_USER1_1 | 2004-05-20 19:02:52.985 |            1 |    3

21 | X_USER2_0 | X_USER2_0 | 2004-05-20 19:02:53.053 |            1 |    3

22 | X_USER2_1 | X_USER2_1 | 2004-05-20 19:02:53.162 |            1 |    3

23 | X_USER3_0 | X_USER3_0 | 2004-05-20 19:02:53.216 |            1 |    3

24 | X_USER3_1 | X_USER3_1 | 2004-05-20 19:02:53.295 |            1 |    3

(16 lignes)

III-C-4-l - Création de rôles et associations avec des utilisateurs

Nous allons créer des rôles de type anonyme :ROLE1, ROLE2,ROLE3… et les associer à des utilisateurs.

1re phase création des rôles. Le fichier FacadeEx2.java est donné ci-dessous avec les modifications :

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2 {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

public Employe createEmploye(String _nom, String _prenom,String _codeInterne)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Employe emp=new Employe();

emp.setNom(_nom);

emp.setPrenom(_prenom);

emp.setUtilisateur2s(new HashSet());

emp.setCodeInterne(_codeInterne);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(emp);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return emp;

}

public Externe createExterne(String _nom, String _prenom,String _societe)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Externe ext=new Externe();

ext.setNom(_nom);

ext.setPrenom(_prenom);

ext.setUtilisateur2s(new HashSet());

ext.setSociete(_societe);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(ext);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return ext;

}

public Utilisateur2 createUtilisateur2(String _login, String _password,int _issupprimable)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Utilisateur2 user=new Utilisateur2();

Personne pers=new Personne();

user.setLogin(_login);

user.setPassword(_password);

user.setIsSupprimable(new Integer(_issupprimable));

user.setDateCM(new java.util.Date());

//user.setIdPers(new Long(0));

user.setConnexions(new HashSet());

user.setRoles(new HashSet());

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

// pour respecter la clause non-null sur la Foreign-key pointant sur personne on

// affecte par defaut la personne 3

Query q2 = session.createQuery("from Personne where idPers = 3");

pers= (Personne) q2.list().get(0);

user.setPers(pers);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return user;

}

public boolean assocPersToUser(Long _idPers, Long _idUser) throws HibernateException {

boolean retour=true;

Utilisateur2 user =new Utilisateur2();

Personne pers = new Personne();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Query q1=session.createQuery("from Utilisateur2 where idUser = :idUser");

Query q2 = session.createQuery("from Personne where idPers = :idPers");

q1.setParameter("idUser", _idUser);

q2.setParameter("idPers", _idPers);

user= (Utilisateur2) q1.list().get(0);

System.out.println("Login = "+user.getLogin());

pers= (Personne) q2.list().get(0);

System.out.println ("Nom de la personne ="+pers.getNom()+" ;idPers = " +pers.getIdPers());

user.setPers((Personne) pers);

//user.setIdPers(emp.getIdPers());

((Set)((Personne)pers).getUtilisateur2s()).add((Utilisateur2) user);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

retour = false;

throw he;

}

finally {

session.close();

}

return retour;

}

public Role createRole(String _role) throws HibernateException    {

Role role=new Role();

role.setNom(_role);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(role);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return role;

}

}

Le code de ClientEx2.java correspondant avec les modifications :

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2 {

public static void main (String argv[]) throws HibernateException {

Employe emp1;

Externe ext1;

Utilisateur2 user1, user2,user3,user4;

FacadeEx2 facadeEx2 = new FacadeEx2();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

// Creation de 4 Employe

emp1=facadeEx2.createEmploye("PASTUREL0","JEAN-LOUIS0","PAST0000");

facadeEx2.createEmploye("PASTUREL1","JEAN-LOUIS1","PAST0001");

facadeEx2.createEmploye("PASTUREL2","JEAN-LOUIS2","PAST0002");

facadeEx2.createEmploye("PASTUREL3","JEAN-LOUIS3","PAST0003");

// Creation de 4 Externe

ext1=facadeEx2.createExterne("EXTERNE0","JEAN-LOUIS0","SOCI0000");

facadeEx2.createExterne("EXTERNE1","JEAN-LOUIS1","SOCI0001");

facadeEx2.createExterne("EXTERNE2","JEAN-LOUIS2","SOCI0002");

facadeEx2.createExterne("EXTERNE3","JEAN-LOUIS3","SOCI0003");

// Creation de User pour Employe

user1= facadeEx2.createUtilisateur2("E_USER0_0","E_USER0_0",(int) 1);

user2=facadeEx2.createUtilisateur2("E_USER0_1","E_USER0_1",(int)1);

facadeEx2.createUtilisateur2("E_USER1_0","E_USER1_0",(int)1);

facadeEx2.createUtilisateur2("E_USER1_1","E_USER1_1",(int)1);

facadeEx2.createUtilisateur2("E_USER2_0","E_USER2_0",(int)1);

facadeEx2.createUtilisateur2("E_USER2_1","E_USER2_1",(int)1);

facadeEx2.createUtilisateur2("E_USER3_0","E_USER3_0",(int)1);

facadeEx2.createUtilisateur2("E_USER3_1","E_USER3_1",(int)1);

// Creation de User pour Externe

user3=facadeEx2.createUtilisateur2("X_USER0_0","X_USER0_0",(int)1);

user4=facadeEx2.createUtilisateur2("X_USER0_1","X_USER0_1",(int)1);

facadeEx2.createUtilisateur2("X_USER1_0","X_USER1_0",(int)1);

facadeEx2.createUtilisateur2("X_USER1_1","X_USER1_1",(int)1);

facadeEx2.createUtilisateur2("X_USER2_0","X_USER2_0",(int)1);

facadeEx2.createUtilisateur2("X_USER2_1","X_USER2_1",(int)1);

facadeEx2.createUtilisateur2("X_USER3_0","X_USER3_0",(int)1);

facadeEx2.createUtilisateur2("X_USER3_1","X_USER3_1",(int)1);

// Associer le user "E_USER_0_0" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user1.getIdUser());

// Associer le user "E_USER_0_1" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user2.getIdUser());

//Associer  le user "X_USER_0_0" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user3.getIdUser());

//Associer  le user "X_USER_0_1" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user4.getIdUser());

// Creation de quelques Roles : ROLE1, ROLE2, ROLE3...

String rolei="ROLE";

for (int i=0;i<10;i++)

{

facadeEx2.createRole(rolei+i);

}

}

}

Après avoir recréé la base à partir du script ex2bis.ddl (voir plus haut), compilé les classes, on peut relancer :

./lanceEx2.ksh

Le résultat dans la base est donné ci-dessous :

essai=> select * from Roles;

idrole |  nom

--------+-------

25 | ROLE0

26 | ROLE1

27 | ROLE2

28 | ROLE3

29 | ROLE4

30 | ROLE5

31 | ROLE6

32 | ROLE7

33 | ROLE8

34 | ROLE9

(10 lignes)

essai=> select * from UTILISATEUR2_ROLES;

iduser | idrole

--------+--------

(0 lignes)

essai=> select * from UTILISATEUR2 order by 1;

iduser |   login   | password  |         datecm          | issuprimable | pers

--------+-----------+-----------+-------------------------+--------------+------

9 | E_USER0_0 | E_USER0_0 | 2004-05-21 16:58:18.323 |            1 |    1

10 | E_USER0_1 | E_USER0_1 | 2004-05-21 16:58:18.413 |            1 |    1

11 | E_USER1_0 | E_USER1_0 | 2004-05-21 16:58:18.486 |            1 |    3

12 | E_USER1_1 | E_USER1_1 | 2004-05-21 16:58:18.498 |            1 |    3

13 | E_USER2_0 | E_USER2_0 | 2004-05-21 16:58:18.516 |            1 |    3

14 | E_USER2_1 | E_USER2_1 | 2004-05-21 16:58:18.534 |            1 |    3

15 | E_USER3_0 | E_USER3_0 | 2004-05-21 16:58:18.563 |            1 |    3

16 | E_USER3_1 | E_USER3_1 | 2004-05-21 16:58:18.611 |            1 |    3

17 | X_USER0_0 | X_USER0_0 | 2004-05-21 16:58:18.654 |            1 |    5

18 | X_USER0_1 | X_USER0_1 | 2004-05-21 16:58:18.70  |            1 |    5

19 | X_USER1_0 | X_USER1_0 | 2004-05-21 16:58:18.741 |            1 |    3

20 | X_USER1_1 | X_USER1_1 | 2004-05-21 16:58:18.792 |            1 |    3

21 | X_USER2_0 | X_USER2_0 | 2004-05-21 16:58:18.84  |            1 |    3

22 | X_USER2_1 | X_USER2_1 | 2004-05-21 16:58:18.936 |            1 |    3

23 | X_USER3_0 | X_USER3_0 | 2004-05-21 16:58:18.986 |            1 |    3

24 | X_USER3_1 | X_USER3_1 | 2004-05-21 16:58:19.058 |            1 |    3

(16lignes)

2e phase : nous allons maintenant réaliser les associations Utilisateurs <-> rôles

iduser

idrole

9

25

9

26

9

27

10

25

10

3

10

34

11

27

11

28

11

29

On a bien des relations many-to-many.

On complète la classe FacadeEx2.java par la méthode qui associe les utilisateurs aux rôles (modifications en fond plus foncé)

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2 {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

public Employe createEmploye(String _nom, String _prenom,String _codeInterne)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Employe emp=new Employe();

emp.setNom(_nom);

emp.setPrenom(_prenom);

emp.setUtilisateur2s(new HashSet());

emp.setCodeInterne(_codeInterne);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(emp);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return emp;

}

public Externe createExterne(String _nom, String _prenom,String _societe)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Externe ext=new Externe();

ext.setNom(_nom);

ext.setPrenom(_prenom);

ext.setUtilisateur2s(new HashSet());

ext.setSociete(_societe);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(ext);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return ext;

}

public Utilisateur2 createUtilisateur2(String _login, String _password,int _issupprimable)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Utilisateur2 user=new Utilisateur2();

Personne pers=new Personne();

user.setLogin(_login);

user.setPassword(_password);

user.setIsSupprimable(new Integer(_issupprimable));

user.setDateCM(new java.util.Date());

//user.setIdPers(new Long(0));

user.setConnexions(new HashSet());

user.setRoles(new HashSet());

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

// pour respecter la clause non-null sur la Foreign-key pointant sur personne on

// affecte par defaut la personne 3

Query q2 = session.createQuery("from Personne where idPers = 3");

pers= (Personne) q2.list().get(0);

user.setPers(pers);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return user;

}

public boolean assocPersToUser(Long _idPers, Long _idUser) throws HibernateException {

boolean retour=true;

Utilisateur2 user =new Utilisateur2();

Personne pers = new Personne();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Query q1=session.createQuery("from Utilisateur2 where idUser = :idUser");

Query q2 = session.createQuery("from Personne where idPers = :idPers");

q1.setParameter("idUser", _idUser);

q2.setParameter("idPers", _idPers);

user= (Utilisateur2) q1.list().get(0);

System.out.println("Login = "+user.getLogin());

pers= (Personne) q2.list().get(0);

System.out.println ("Nom de la personne ="+pers.getNom()+" ;idPers = " +pers.getIdPers());

user.setPers((Personne) pers);

//user.setIdPers(emp.getIdPers());

((Set)((Personne)pers).getUtilisateur2s()).add((Utilisateur2) user);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

retour = false;

throw he;

}

finally {

session.close();

}

return retour;

}

public Role createRole(String _role) throws HibernateException    {

Role role=new Role();

role.setNom(_role);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(role);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return role;

}

public boolean assocUtilisateur2Roles(Long _idUser, Long _idRole)

throws HibernateException    {

boolean retour=true;

Role rol=new Role();

Utilisateur2 util=new Utilisateur2();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Query q1=session.createQuery("from Utilisateur2 where idUser = :idUser");

Query q2 = session.createQuery("from Role where idRole = :idRole");

q1.setParameter("idUser", _idUser);

q2.setParameter("idRole", _idRole);

util=(Utilisateur2) q1.list().get(0);

System.out.println("Login = "+util.getLogin());

rol=(Role) q2.list().get(0);

System.out.println ("Nom du Role ="+rol.getNom());

((Set)util.getRoles()).add((Role) rol);

((Set)rol.getUtilisateur2s()).add((Utilisateur2) util);

session.save(rol);

session.save(util);

tx.commit();

}

catch (HibernateException he) {

retour=false;

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return retour;

}

}

Écriture de la classe client pour réaliser ces associations ClientEx2.java, dont les modifications sont identifiées en fond plus foncé :

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2 {

public static void main (String argv[]) throws HibernateException {

Employe emp1;

Externe ext1;

Utilisateur2 user1, user2,user3,user4;

FacadeEx2 facadeEx2 = new FacadeEx2();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

// Creation de 4 Employe

emp1=facadeEx2.createEmploye("PASTUREL0","JEAN-LOUIS0","PAST0000");

facadeEx2.createEmploye("PASTUREL1","JEAN-LOUIS1","PAST0001");

facadeEx2.createEmploye("PASTUREL2","JEAN-LOUIS2","PAST0002");

facadeEx2.createEmploye("PASTUREL3","JEAN-LOUIS3","PAST0003");

// Creation de 4 Externe

ext1=facadeEx2.createExterne("EXTERNE0","JEAN-LOUIS0","SOCI0000");

facadeEx2.createExterne("EXTERNE1","JEAN-LOUIS1","SOCI0001");

facadeEx2.createExterne("EXTERNE2","JEAN-LOUIS2","SOCI0002");

facadeEx2.createExterne("EXTERNE3","JEAN-LOUIS3","SOCI0003");

// Creation de User pour Employe

user1= facadeEx2.createUtilisateur2("E_USER0_0","E_USER0_0",(int) 1);

user2=facadeEx2.createUtilisateur2("E_USER0_1","E_USER0_1",(int)1);

facadeEx2.createUtilisateur2("E_USER1_0","E_USER1_0",(int)1);

facadeEx2.createUtilisateur2("E_USER1_1","E_USER1_1",(int)1);

facadeEx2.createUtilisateur2("E_USER2_0","E_USER2_0",(int)1);

facadeEx2.createUtilisateur2("E_USER2_1","E_USER2_1",(int)1);

facadeEx2.createUtilisateur2("E_USER3_0","E_USER3_0",(int)1);

facadeEx2.createUtilisateur2("E_USER3_1","E_USER3_1",(int)1);

// Creation de User pour Externe

user3=facadeEx2.createUtilisateur2("X_USER0_0","X_USER0_0",(int)1);

user4=facadeEx2.createUtilisateur2("X_USER0_1","X_USER0_1",(int)1);

facadeEx2.createUtilisateur2("X_USER1_0","X_USER1_0",(int)1);

facadeEx2.createUtilisateur2("X_USER1_1","X_USER1_1",(int)1);

facadeEx2.createUtilisateur2("X_USER2_0","X_USER2_0",(int)1);

facadeEx2.createUtilisateur2("X_USER2_1","X_USER2_1",(int)1);

facadeEx2.createUtilisateur2("X_USER3_0","X_USER3_0",(int)1);

facadeEx2.createUtilisateur2("X_USER3_1","X_USER3_1",(int)1);

// Associer le user "E_USER_0_0" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user1.getIdUser());

// Associer le user "E_USER_0_1" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user2.getIdUser());

//Associer  le user "X_USER_0_0" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user3.getIdUser());

//Associer  le user "X_USER_0_1" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user4.getIdUser());

// Creation de quelqes Roles : ROLE1, ROLE2, ROLE3...

String rolei="ROLE";

for (int i=0;i<10;i++)

{

facadeEx2.createRole(rolei+i);

}

// Creation d'associations entre Utilisateurs et Roles :

facadeEx2.assocUtilisateur2Roles(new Long(9), new Long(25));

facadeEx2.assocUtilisateur2Roles(new Long(9), new Long(26));

facadeEx2.assocUtilisateur2Roles(new Long(9), new Long(27));

facadeEx2.assocUtilisateur2Roles(new Long(10), new Long(25));

facadeEx2.assocUtilisateur2Roles(new Long(10), new Long(33));

facadeEx2.assocUtilisateur2Roles(new Long(10), new Long(34));

facadeEx2.assocUtilisateur2Roles(new Long(11), new Long(27));

facadeEx2.assocUtilisateur2Roles(new Long(11), new Long(28));

facadeEx2.assocUtilisateur2Roles(new Long(11), new Long(29));

}

}

Après avoir recréé la base à partir du script ex2bis.ddl (voir plus haut), compilé les classes, on peut relancer :

./lanceEx2.ksh

Et la preuve dans la base :

essai=> select * from UTILISATEUR2_ROLES;

iduser | idrole

--------+--------

9 |     25

9 |     26

9 |     27

10 |     25

10 |     33

10 |     34

17 |     27

17 |     28

18 |     29

(9lignes)

conforme au tableau donné plus haut.

III-C-4-m - Puissance de la clause de cascade

Nous allons écrire une méthode de delete d'une personne.
Le comportement attendu à la destruction de la personne est le suivant :

- destruction de l'utilisateur associé à la personne ;

- destruction des connexions ;

- destruction des liens utilisateur2_role sans destruction du rôle.

Les clauses de cascades sont définies dans les fichiers de mapping *.hbm.xml décrits plus haut.
Pour tester la puissance des clauses de cascade, nous allons introduire des connexions sur des utilisateurs (les utilisateurs 9 et 10 liés à la Personne 1 que l'on va détruire, et positionner des connexions sur les utilisateurs 17 et 18 liés à la personne 5 qui ne sera pas détruite).

La classe FacadeEx2.java :

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2 {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

public Employe createEmploye(String _nom, String _prenom,String _codeInterne)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Employe emp=new Employe();

emp.setNom(_nom);

emp.setPrenom(_prenom);

emp.setUtilisateur2s(new HashSet());

emp.setCodeInterne(_codeInterne);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(emp);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return emp;

}

public Externe createExterne(String _nom, String _prenom,String _societe)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Externe ext=new Externe();

ext.setNom(_nom);

ext.setPrenom(_prenom);

ext.setUtilisateur2s(new HashSet());

ext.setSociete(_societe);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(ext);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return ext;

}

public Utilisateur2 createUtilisateur2(String _login, String _password,int _issupprimable)

throws HibernateException {

//Initialisation d un Employe sous-classe de Personne

Utilisateur2 user=new Utilisateur2();

Personne pers=new Personne();

user.setLogin(_login);

user.setPassword(_password);

user.setIsSupprimable(new Integer(_issupprimable));

user.setDateCM(new java.util.Date());

//user.setIdPers(new Long(0));

user.setConnexions(new HashSet());

user.setRoles(new HashSet());

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

// pour respecter la clause non-null sur la Foreign-key pointant sur personne on

// affecte par defaut la personne 3

Query q2 = session.createQuery("from Personne where idPers = 3");

pers= (Personne) q2.list().get(0);

user.setPers(pers);

session.save(user);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return user;

}

public boolean assocPersToUser(Long _idPers, Long _idUser) throws HibernateException {

boolean retour=true;

Utilisateur2 user =new Utilisateur2();

Personne pers = new Personne();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Query q1=session.createQuery("from Utilisateur2 where idUser = :idUser");

Query q2 = session.createQuery("from Personne where idPers = :idPers");

q1.setParameter("idUser", _idUser);

q2.setParameter("idPers", _idPers);

user= (Utilisateur2) q1.list().get(0);

System.out.println("Login = "+user.getLogin());

pers= (Personne) q2.list().get(0);

System.out.println ("Nom de la personne ="+pers.getNom()+" ;idPers = " +pers.getIdPers());

user.setPers((Personne) pers);

//user.setIdPers(emp.getIdPers());

((Set)((Personne)pers).getUtilisateur2s()).add((Utilisateur2) user);

session.save(user);

session.save(pers);

session.flush();

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

retour = false;

throw he;

}

finally {

session.close();

}

return retour;

}

public Role createRole(String _role) throws HibernateException    {

Role role=new Role();

role.setNom(_role);

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

session.save(role);

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return role;

}

public boolean assocUtilisateur2Roles(Long _idUser, Long _idRole)

throws HibernateException    {

boolean retour=true;

Role rol=new Role();

Utilisateur2 util=new Utilisateur2();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

/*Query q1=session.createQuery("from Utilisateur2 where idUser = :idUser");

Query q2 = session.createQuery("from Role where idRole = :idRole");

q1.setParameter("idUser", _idUser);

q2.setParameter("idRole", _idRole);*/

util=(Utilisateur2) session.load(Utilisateur2.class,_idUser);

System.out.println("Login = "+util.getLogin());

//rol=(Role) q2.list().get(0);

rol=(Role) session.load(Role.class,_idRole);

System.out.println ("Nom du Role ="+rol.getNom());

((Set)util.getRoles()).add((Role) rol);

((Set)rol.getUtilisateur2s()).add((Utilisateur2) util);

session.save(rol);

session.save(util);

session.flush();

tx.commit();

}

catch (HibernateException he) {

retour=false;

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return retour;

}

public boolean deletePersonne(Long _idPers) throws HibernateException    {

boolean retour=true;

Personne pers=new Personne();

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

/*Query q1=session.createQuery("from Personne where idPers = :idPers");

q1.setParameter("idPers", _idPers);

pers=(Personne) q1.list().get(0);*/

pers=(Personne) session.load(Personne.class,_idPers);

System.out.println("Login = "+pers.getNom());

session.delete(pers);

session.flush();

tx.commit();

}

catch (HibernateException he) {

retour=false;

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return retour;

}

public Connexion createConnexion(Long _idUser) throws HibernateException

{

Connexion con =new Connexion();

con.setDateDeb(new Date());

con.setDateDeb(new Date());

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Utilisateur2 util=(Utilisateur2) session.load(Utilisateur2.class,_idUser);

con.setUtil(util);

((Set)util.getConnexions()).add((Connexion) con);

session.saveOrUpdate(util);

session.saveOrUpdate(con);

//session.flush();

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return con;

}

}

La classe ClientEx2.java :

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2 {

public static void main (String argv[]) throws HibernateException {

Employe emp1;

Externe ext1;

Utilisateur2 user1, user2,user3,user4;

FacadeEx2 facadeEx2 = new FacadeEx2();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

// Creation de 4 Employe

emp1=facadeEx2.createEmploye("PASTUREL0","JEAN-LOUIS0","PAST0000");

facadeEx2.createEmploye("PASTUREL1","JEAN-LOUIS1","PAST0001");

facadeEx2.createEmploye("PASTUREL2","JEAN-LOUIS2","PAST0002");

facadeEx2.createEmploye("PASTUREL3","JEAN-LOUIS3","PAST0003");

// Creation de 4 Externe

ext1=facadeEx2.createExterne("EXTERNE0","JEAN-LOUIS0","SOCI0000");

facadeEx2.createExterne("EXTERNE1","JEAN-LOUIS1","SOCI0001");

facadeEx2.createExterne("EXTERNE2","JEAN-LOUIS2","SOCI0002");

facadeEx2.createExterne("EXTERNE3","JEAN-LOUIS3","SOCI0003");

// Creation de User pour Employe

user1= facadeEx2.createUtilisateur2("E_USER0_0","E_USER0_0",(int) 1);

user2=facadeEx2.createUtilisateur2("E_USER0_1","E_USER0_1",(int)1);

facadeEx2.createUtilisateur2("E_USER1_0","E_USER1_0",(int)1);

facadeEx2.createUtilisateur2("E_USER1_1","E_USER1_1",(int)1);

facadeEx2.createUtilisateur2("E_USER2_0","E_USER2_0",(int)1);

facadeEx2.createUtilisateur2("E_USER2_1","E_USER2_1",(int)1);

facadeEx2.createUtilisateur2("E_USER3_0","E_USER3_0",(int)1);

facadeEx2.createUtilisateur2("E_USER3_1","E_USER3_1",(int)1);

// Creation de User pour Externe

user3=facadeEx2.createUtilisateur2("X_USER0_0","X_USER0_0",(int)1);

user4=facadeEx2.createUtilisateur2("X_USER0_1","X_USER0_1",(int)1);

facadeEx2.createUtilisateur2("X_USER1_0","X_USER1_0",(int)1);

facadeEx2.createUtilisateur2("X_USER1_1","X_USER1_1",(int)1);

facadeEx2.createUtilisateur2("X_USER2_0","X_USER2_0",(int)1);

facadeEx2.createUtilisateur2("X_USER2_1","X_USER2_1",(int)1);

facadeEx2.createUtilisateur2("X_USER3_0","X_USER3_0",(int)1);

facadeEx2.createUtilisateur2("X_USER3_1","X_USER3_1",(int)1);

// Associer le user "E_USER_0_0" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user1.getIdUser());

System.out.println ("assoc de l emp :"+emp1.getIdPers()+" à l User : "+ user1.getIdUser());

// Associer le user "E_USER_0_1" a l'employe "PASTUREL0"

facadeEx2.assocPersToUser(emp1.getIdPers(), user2.getIdUser());

System.out.println ("assoc de l emp :"+emp1.getIdPers()+" à l User : "+ user2.getIdUser());

//Associer  le user "X_USER_0_0" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user3.getIdUser());

System.out.println ("assoc de l emp :"+ext1.getIdPers()+" à l User : "+ user3.getIdUser());

//Associer  le user "X_USER_0_1" a "EXTERNE0"

facadeEx2.assocPersToUser(ext1.getIdPers(), user4.getIdUser());

System.out.println ("assoc de l emp :"+ext1.getIdPers()+" à l User : "+ user4.getIdUser());

// Creation de quelqes Roles : ROLE1, ROLE2, ROLE3...

String rolei="ROLE";

for (int i=0;i<10;i++)

{

facadeEx2.createRole(rolei+i);

}

// Creation d'associations entre Utilisateurs et Roles :

facadeEx2.assocUtilisateur2Roles(new Long(9), new Long(25));

facadeEx2.assocUtilisateur2Roles(new Long(9), new Long(26));

facadeEx2.assocUtilisateur2Roles(new Long(9), new Long(27));

facadeEx2.assocUtilisateur2Roles(new Long(10), new Long(25));

facadeEx2.assocUtilisateur2Roles(new Long(10), new Long(33));

facadeEx2.assocUtilisateur2Roles(new Long(10), new Long(34));

facadeEx2.assocUtilisateur2Roles(new Long(17), new Long(27));

facadeEx2.assocUtilisateur2Roles(new Long(17), new Long(28));

facadeEx2.assocUtilisateur2Roles(new Long(18), new Long(29));

// creation de Connexions sur Utilisateur 9, 10 et 17 et 18

facadeEx2.createConnexion(new Long(9));

facadeEx2.createConnexion(new Long(9));

facadeEx2.createConnexion(new Long(10));

facadeEx2.createConnexion(new Long(10));

facadeEx2.createConnexion(new Long(17));

facadeEx2.createConnexion(new Long(17));

facadeEx2.createConnexion(new Long(17));

facadeEx2.createConnexion(new Long(18));

facadeEx2.createConnexion(new Long(18));

facadeEx2.createConnexion(new Long(18));

facadeEx2.createConnexion(new Long(18));

// Suppression de la Personne dont le idPers = 1

facadeEx2.deletePersonne(new Long(1));

}

}

On va faire deux manipulations :

- la première va consister à ne pas lancer la dernière ligne de ClientEx2 en la diésant pour avoir un état avant ;

- la deuxième intégrera la dernière ligne et nous pourrons faire des comparaisons.

Première manipulation :
Après compilation des classes, rejeu du script de création de la base et exécution du client ClientEx2 , on a la database dans l'état suivant :

essai=> select * from Personnes order by 1;

idpers | sousclasse |    nom    |   prenom    | codeinterne | societe

--------+------------+-----------+-------------+-------------+----------

1 | E          | PASTUREL0 | JEAN-LOUIS0 | PAST0000    |

2 | E          | PASTUREL1 | JEAN-LOUIS1 | PAST0001    |

3 | E          | PASTUREL2 | JEAN-LOUIS2 | PAST0002    |

4 | E          | PASTUREL3 | JEAN-LOUIS3 | PAST0003    |

5 | X          | EXTERNE0  | JEAN-LOUIS0 |             | SOCI0000

6 | X          | EXTERNE1  | JEAN-LOUIS1 |             | SOCI0001

7 | X          | EXTERNE2  | JEAN-LOUIS2 |             | SOCI0002

8 | X          | EXTERNE3  | JEAN-LOUIS3 |             | SOCI0003

(8 lignes)

select * from Utilisateur2 order by 1;

iduser |   login   | password  |         datecm          | issuprimable | pers

--------+-----------+-----------+-------------------------+--------------+------

9 | E_USER0_0 | E_USER0_0 | 2004-05-21 21:43:59.31  |            1 |    1

10 | E_USER0_1 | E_USER0_1 | 2004-05-21 21:43:59.412 |            1 |    1

11 | E_USER1_0 | E_USER1_0 | 2004-05-21 21:43:59.468 |            1 |    3

12 | E_USER1_1 | E_USER1_1 | 2004-05-21 21:43:59.485 |            1 |    3

13 | E_USER2_0 | E_USER2_0 | 2004-05-21 21:43:59.526 |            1 |    3

14 | E_USER2_1 | E_USER2_1 | 2004-05-21 21:43:59.545 |            1 |    3

15 | E_USER3_0 | E_USER3_0 | 2004-05-21 21:43:59.567 |            1 |    3

16 | E_USER3_1 | E_USER3_1 | 2004-05-21 21:43:59.614 |            1 |    3

17 | X_USER0_0 | X_USER0_0 | 2004-05-21 21:43:59.645 |            1 |    5

18 | X_USER0_1 | X_USER0_1 | 2004-05-21 21:43:59.691 |            1 |    5

19 | X_USER1_0 | X_USER1_0 | 2004-05-21 21:43:59.735 |            1 |    3

20 | X_USER1_1 | X_USER1_1 | 2004-05-21 21:43:59.788 |            1 |    3

21 | X_USER2_0 | X_USER2_0 | 2004-05-21 21:43:59.843 |            1 |    3

22 | X_USER2_1 | X_USER2_1 | 2004-05-21 21:43:59.949 |            1 |    3

23 | X_USER3_0 | X_USER3_0 | 2004-05-21 21:43:59.997 |            1 |    3

24 | X_USER3_1 | X_USER3_1 | 2004-05-21 21:44:00.064 |            1 |    3

(16lignes)

essai=> select * from Roles;

idrole |  nom

--------+-------

25 | ROLE0

26 | ROLE1

27 | ROLE2

28 | ROLE3

29 | ROLE4

30 | ROLE5

31 | ROLE6

32 | ROLE7

33 | ROLE8

34 | ROLE9

(10lignes)

essai=> select * from Utilisateur2_Roles;

idrole | iduser

--------+--------

25 |      9

26 |      9

27 |      9

25 |     10

33 |     10

34 |     10

27 |     17

28 |     17

29 |     18

(9lignes)

essai=> select * from Connexions;

idcnx |  datedeb   |  datefin   | util

-------+------------+------------+------

35 | 2004-05-21 | 2004-05-21 |    9

36 | 2004-05-21 | 2004-05-21 |    9

37 | 2004-05-21 | 2004-05-21 |   10

38 | 2004-05-21 | 2004-05-21 |   10

39 | 2004-05-21 | 2004-05-21 |   17

40 | 2004-05-21 | 2004-05-21 |   17

41 | 2004-05-21 | 2004-05-21 |   17

42 | 2004-05-21 | 2004-05-21 |   18

43 | 2004-05-21 | 2004-05-21 |   18

44 | 2004-05-21 | 2004-05-21 |   18

45 | 2004-05-21 | 2004-05-21 |   18

(11 lignes)

Nous allons détruire l'Employe 1
. 2e opération, on dé-dièse la dernière commande de ClientEx2, on recompile les classes, on rejoue le script de création de la database et on lance le ClientEx2.

essai=> select * from Personnes order by 1;

idpers | sousclasse |    nom    |   prenom    | codeinterne | societe

--------+------------+-----------+-------------+-------------+----------

2 | E          | PASTUREL1 | JEAN-LOUIS1 | PAST0001    |

3 | E          | PASTUREL2 | JEAN-LOUIS2 | PAST0002    |

4 | E          | PASTUREL3 | JEAN-LOUIS3 | PAST0003    |

5 | X          | EXTERNE0  | JEAN-LOUIS0 |             | SOCI0000

6 | X          | EXTERNE1  | JEAN-LOUIS1 |             | SOCI0001

7 | X          | EXTERNE2  | JEAN-LOUIS2 |             | SOCI0002

8 | X          | EXTERNE3  | JEAN-LOUIS3 |             | SOCI0003

(7 lignes)

Plus de Personne dont le idPers = 1 !

eessai=> select * from Utilisateur2 order by 1;

iduser |   login   | password  |         datecm          | issuprimable | pers

--------+-----------+-----------+-------------------------+--------------+------

11 | E_USER1_0 | E_USER1_0 | 2004-05-21 21:58:13.29  |            1 |    3

12 | E_USER1_1 | E_USER1_1 | 2004-05-21 21:58:13.303 |            1 |    3

13 | E_USER2_0 | E_USER2_0 | 2004-05-21 21:58:13.321 |            1 |    3

14 | E_USER2_1 | E_USER2_1 | 2004-05-21 21:58:13.339 |            1 |    3

15 | E_USER3_0 | E_USER3_0 | 2004-05-21 21:58:13.366 |            1 |    3

16 | E_USER3_1 | E_USER3_1 | 2004-05-21 21:58:13.409 |            1 |    3

17 | X_USER0_0 | X_USER0_0 | 2004-05-21 21:58:13.449 |            1 |    5

18 | X_USER0_1 | X_USER0_1 | 2004-05-21 21:58:13.50  |            1 |    5

19 | X_USER1_0 | X_USER1_0 | 2004-05-21 21:58:13.543 |            1 |    3

20 | X_USER1_1 | X_USER1_1 | 2004-05-21 21:58:13.601 |            1 |    3

21 | X_USER2_0 | X_USER2_0 | 2004-05-21 21:58:13.663 |            1 |    3

22 | X_USER2_1 | X_USER2_1 | 2004-05-21 21:58:13.754 |            1 |    3

23 | X_USER3_0 | X_USER3_0 | 2004-05-21 21:58:13.804 |            1 |    3

24 | X_USER3_1 | X_USER3_1 | 2004-05-21 21:58:13.868 |            1 |    3

(14 lignes)

Et là plus d'Utilisateur2 dont l'idUser = 9 ou 10

essai=> select * from Utilisateur2_Roles;

idrole | iduser

--------+--------

27 |     17

28 |     17

29 |     18

(3 lignes)

Les associations aux rôles des utilisateurs 9 et 10 n'existent plus, les autres n'ont pas été touchées.

essai=> select * from Roles;

idrole |  nom

--------+-------

25 | ROLE0

26 | ROLE1

27 | ROLE2

28 | ROLE3

29 | ROLE4

30 | ROLE5

31 | ROLE6

32 | ROLE7

33 | ROLE8

34 | ROLE9

(10lignes)

Les rôles n'ont pas été touchés (comportement attendu et normal).

essai=> select * from Connexions;

idcnx |  datedeb   |  datefin   | util

-------+------------+------------+------

39 | 2004-05-21 | 2004-05-21 |   17

40 | 2004-05-21 | 2004-05-21 |   17

41 | 2004-05-21 | 2004-05-21 |   17

42 | 2004-05-21 | 2004-05-21 |   18

43 | 2004-05-21 | 2004-05-21 |   18

44 | 2004-05-21 | 2004-05-21 |   18

45 | 2004-05-21 | 2004-05-21 |   18

(7lignes)

Les connexions des utilisateurs 9 et 10 ont été supprimées, alors que celles des utilisateurs 17 et 18 existent encore (comportement attendu et normal).

Tout ceci est obtenu à l'aide de la clause cascade= « all-delete-orphan » définie dans les fichiers de mapping hbm.xml dans les relations one-to-many.

Extrait de Utilisateur2.hbm.xml :

<set name="connexions" inverse="true" cascade="all-delete-orphan" >

<key column="util" />

<one-to-many class="utilisateur2.Connexion"/>

</set>

Extrait de Personne.hbm.xml :

<set name="utilisateur2s" inverse="true" cascade="all-delete-orphan" >

<key column="pers"/>

<one-to-many class="utilisateur2.Utilisateur2" />

</set>

Dans les relations many-to-many, c'est la contrainte de foreign key qui traite la suppression des relations orphelines dans la table des associations.

III-C-5 - HQL et APICriteria

Dans l'exemple précédent, nous avons à peine effleuré le langage HQL (Hibernate Query Langage), nous allons l'utiliser dans un exemple assez simple de requête.

Le but de cet exemple est de trouver la liste des personnes et utilisateurs associés qui se sont déjà connectés (qui ont au moins une ligne dans la table Connexions de notre exemple).
Pour ne pas polluer l'exemple utilisé auparavant, nous allons recréer une Facade et un Client Test spécifique.

III-C-5-a - Traitons d'abord le cas d'utilisation HQL :

Le code de la classe FacadeEx2Bis.java est donnée ci-dessous :

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2Bis {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

/* Lister les utilisateurs qui ont ete deja connectes

but : tester HQL

*/

public TriString[] utilsConnectesHQL()  throws HibernateException {

TriString[] tabTriString ;

ArrayList al=new ArrayList();

String str1,str2,str3;

Integer cpt;

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

// La requete HQL

Iterator it1 = session.iterate("select pers.nom,pers.prenom,util.login,count(con) "+

"from Personne as pers "+

"join pers.utilisateur2s as util "+

"join util.connexions as con "+

" group by pers.nom, pers.prenom,util.login " +

" having count(con)>0");

int i=0;

//

while (it1.hasNext()) {

Object[] tuple = (Object[]) it1.next();

str1=(String) tuple[0];

str2=(String) tuple[1];

str3=(String) tuple[2];

cpt=(Integer) tuple[3];

al.add(i,new TriString(str1,str2,str3,cpt));

i++;

}

// On  cree un tableau de la taille de l'it1 pour accueillir les elements

System.out.println ("Nombre de lignes retournees : "+ i);

tabTriString=new TriString[i];

// on remplit le tableau

for (int j=0;j<i;j++)

{

tabTriString[j]=(TriString) al.get(j);

}

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return tabTriString;

}

}

Une fois de plus, on notera que la requête adresse les objets et les attributs des objets et non directement les tables. Noter la puissance des join qui évite d'écrire des clauses « where ». On a en effet les liens suivants :
Personnne one-to-many => Utilisateur2 one-to-many => Connexion que retraduisent les « join » de la requête HQL.

La classe ClientEx2Bis.java correspondante :

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2Bis {

public static void main (String argv[]) throws HibernateException {

FacadeEx2Bis facadeEx2 = new FacadeEx2Bis();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

TriString[] triStr=facadeEx2.utilsConnectesHQL();

// affichage des resultats dans la console :

System.out.println("Personne.nom    | Personne.prenom | Utilisateur2.login | nbConnexions");

for (int i=0;i<triStr.length;i++) {

System.out.println(triStr[i].string1+" | "+triStr[i].string2+" | "

+triStr[i].string3+" | "+triStr[i].cpt.toString());

}

}

}

La classe utilitaire TriString.java utilisée :

package utilisateur2;

public class TriString {

public String string1="";

public String string2="";

public String string3="";

public Integer cpt;

public TriString(String str1,String str2,String str3,Integer _cpt)

{

string1=str1;

string2=str2;

string3=str3;

cpt=_cpt;

}

}

Comme d'habitude on compile l'ensemble des classes, mais on ne relance pas la création de la database, on l'utilise telle que l'avait laissée le dernier exemple.

Les tables sont dans l'état suivant :

essai=>select * from Personnes order by 1;

idpers | sousclasse |    nom    |   prenom    | codeinterne | societe

--------+------------+-----------+-------------+-------------+----------

2 | E          | PASTUREL1 | JEAN-LOUIS1 | PAST0001    |

3 | E          | PASTUREL2 | JEAN-LOUIS2 | PAST0002    |

4 | E          | PASTUREL3 | JEAN-LOUIS3 | PAST0003    |

5 | X          | EXTERNE0  | JEAN-LOUIS0 |             | SOCI0000

6 | X          | EXTERNE1  | JEAN-LOUIS1 |             | SOCI0001

7 | X          | EXTERNE2  | JEAN-LOUIS2 |             | SOCI0002

8 | X          | EXTERNE3  | JEAN-LOUIS3 |             | SOCI0003

(7 lignes)

essai=> select * from Utilisateur2 order by 1;

iduser |   login   | password  |         datecm          | issuprimable | pers

--------+-----------+-----------+-------------------------+--------------+------

11 | E_USER1_0 | E_USER1_0 | 2004-05-21 21:58:13.29  |            1 |    3

12 | E_USER1_1 | E_USER1_1 | 2004-05-21 21:58:13.303 |            1 |    3

13 | E_USER2_0 | E_USER2_0 | 2004-05-21 21:58:13.321 |            1 |    3

14 | E_USER2_1 | E_USER2_1 | 2004-05-21 21:58:13.339 |            1 |    3

15 | E_USER3_0 | E_USER3_0 | 2004-05-21 21:58:13.366 |            1 |    3

16 | E_USER3_1 | E_USER3_1 | 2004-05-21 21:58:13.409 |            1 |    3

17 | X_USER0_0 | X_USER0_0 | 2004-05-21 21:58:13.449 |            1 |    5

18 | X_USER0_1 | X_USER0_1 | 2004-05-21 21:58:13.50  |            1 |    5

19 | X_USER1_0 | X_USER1_0 | 2004-05-21 21:58:13.543 |            1 |    3

20 | X_USER1_1 | X_USER1_1 | 2004-05-21 21:58:13.601 |            1 |    3

21 | X_USER2_0 | X_USER2_0 | 2004-05-21 21:58:13.663 |            1 |    3

22 | X_USER2_1 | X_USER2_1 | 2004-05-21 21:58:13.754 |            1 |    3

23 | X_USER3_0 | X_USER3_0 | 2004-05-21 21:58:13.804 |            1 |    3

24 | X_USER3_1 | X_USER3_1 | 2004-05-21 21:58:13.868 |            1 |    3

(14 lignes)

essai=> select * from connexions order by 1;

idcnx |  datedeb   |  datefin   | util

-------+------------+------------+------

39 | 2004-05-21 | 2004-05-21 |   17

40 | 2004-05-21 | 2004-05-21 |   17

41 | 2004-05-21 | 2004-05-21 |   17

42 | 2004-05-21 | 2004-05-21 |   18

43 | 2004-05-21 | 2004-05-21 |   18

44 | 2004-05-21 | 2004-05-21 |   18

45 | 2004-05-21 | 2004-05-21 |   18

(7 lignes)

On voit donc que la Personne 5 (EXTERNE0 , JEAN-LOUIS0) qui possède les Utilisateur2 17 et 18 (resp login X_USER0_0 et X_USER0_1) est la seule à posséder des connexions (4 pour le login X_USER0_1 et 3 pour le login X_USER0_0).

Le lanceur du client ClientEx2Bis.java est donné ci-dessous :
Fichier lanceEx2Bis.ksh :

#!/bin/bash

#Déclaration des CLASSPATH HIBERNATE

HIBERNATE_HOME=/opt/hibernate-2.1

CLASSPATH=.:./utilisateur2 :$HIBERNATE_HOME/hibernate2.jar

for i in ` ls $HIBERNATE_HOME/lib/*.jar`; do

CLASSPATH=$CLASSPATH :$i;

done

echo CLASSPATH=$CLASSPATH

export CLASSPATH

java -classpath $CLASSPATH utilisateur2.ClientEx2Bis

Le lancement se fait par :

./lanceEx2Bis.ksh

Dans la console Hibernate on peut noter la requête SQL générée (dans le fichier hibernate.properties, on a mis la clause : hibernate.show_sql true) :

Hibernate : select personne0_.nom as x0_0_, personne0_.prenom as x1_0_, utilisateu1_.login as x2_0_,

count(connexions2_.idCnx) as x3_0_

from PERSONNES personne0_

inner join UTILISATEUR2 utilisateu1_ on personne0_.idPers=utilisateu1_.pers

inner join CONNEXIONS connexions2_ on utilisateu1_.idUser=connexions2_.util

group by  personne0_.nom , personne0_.prenom , utilisateu1_.login

having (count(connexions2_.idCnx)>0)

Si on exécute cette requête SQL dans la base on a le résultat suivant :

essai=> select personne0_.nom as x0_0_, personne0_.prenom as x1_0_, utilisateu1_.login as x2_0_,

count(connexions2_.idCnx) as x3_0_

from PERSONNES personne0_

inner join UTILISATEUR2 utilisateu1_ on personne0_.idPers=utilisateu1_.pers

inner join CONNEXIONS connexions2_ on utilisateu1_.idUser=connexions2_.util

group by  personne0_.nom , personne0_.prenom , utilisateu1_.login

having (count(connexions2_.idCnx)>0);

x0_0_   |    x1_0_    |   x2_0_   | x3_0_

----------+-------------+-----------+-------

EXTERNE0 | JEAN-LOUIS0 | X_USER0_1 |     4

EXTERNE0 | JEAN-LOUIS0 | X_USER0_0 |     3

(2lignes)

Ce qui est conforme au résultat attendu du lancement de la classe ClientEx2Bis récupéré dans la console :

Personne.nom    | Personne.prenom | Utilisateur2.login | nbConnexions

EXTERNE0 | JEAN-LOUIS0 | X_USER0_1 | 4

EXTERNE0 | JEAN-LOUIS0 | X_USER0_0 | 3

III-C-5-b - Traitement avec l'API Criteria

Pour ceux qui ne sont pas habitués à manipuler le langage SQL (orienté relationnel), Hibernate propose comme alternative une API (Criteria) pour exécuter des requêtes.
L'API Criteria ne propose en natif que des requêtes ne portant que sur une classe persistante (Session.createCriteria(Class PersistentClass), ce qui malheureusement ne nous convient pas très bien dans notre cas.
Nous allons exécuter une requête qui va retourner le nom des personnes (et autant de fois que de connexions) ayant eu une connexion. Il faudrait compléter la requête par une requête sur la classe persistante Utilisateur2. Je laisse le soin au lecteur de compléter cette requête.
La classe FacadeEx2Bis.java modifiée :

package utilisateur2;

import utilisateur2.*;

import java.util.*;

import net.sf.hibernate.expression.Expression;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class FacadeEx2Bis {

private SessionFactory sessFactory;

public void configure() throws HibernateException {

sessFactory = new Configuration()

.addClass(utilisateur2.Connexion.class)

.addClass(Personne.class)

.addClass(Role.class)

.addClass(Utilisateur2.class)

.buildSessionFactory();

System.out.println("Configuration termine OK \n");

}

/* Lister les utilisateurs qui ont ete deja connectes

but : tester HQL

*/

public TriString[] utilsConnectesHQL()  throws HibernateException {

TriString[] tabTriString ;

ArrayList al=new ArrayList();

String str1,str2,str3;

Integer cpt;

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

Iterator it1 = session.iterate("select pers.nom,pers.prenom,util.login,count(con) "+

"from Personne as pers "+

"join pers.utilisateur2s as util "+

"join util.connexions as con "+

" group by pers.nom, pers.prenom,util.login " +

" having count(con)>0");

int i=0;

//

while (it1.hasNext()) {

Object[] tuple = (Object[]) it1.next();

str1=(String) tuple[0];

str2=(String) tuple[1];

str3=(String) tuple[2];

cpt=(Integer) tuple[3];

al.add(i,new TriString(str1,str2,str3,cpt));

i++;

}

// On  cree un tableau de la taille de l'it1 pour accueillir les elements

System.out.println ("Nombre de lignes retournees : "+ i);

tabTriString=new TriString[i];

// on remplit le tableau

for (int j=0;j<i;j++)

{

tabTriString[j]=(TriString) al.get(j);

}

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

return tabTriString;

}

public void utilsConnectesCriteria()  throws HibernateException {

TriString[] tabTriString ;

ArrayList al=new ArrayList();

String str1,str2,str3;

Integer cpt;

Session session = sessFactory.openSession();

Transaction tx = null;

try {

tx = session.beginTransaction();

List  personnes=session.createCriteria(Personne.class)

.createCriteria("utilisateur2s").createCriteria("connexions")

.add(Expression.isNotNull("idCnx"))

.list();

int i=0;

i=personnes.size();

System.out.println("Nom personne | prenom ");

for (int j=0;j<i;j++)

{

System.out.println(((Personne)personnes.get(j)).getNom()+" | "

+((Personne)personnes.get(j)).getPrenom());

}

tx.commit();

}

catch (HibernateException he) {

if (tx!=null) tx.rollback();

throw he;

}

finally {

session.close();

}

//return tabTriString;

}

}

et la classe ClientEx2Bis.java modifiée :

package utilisateur2;

import utilisateur2.*;

import net.sf.hibernate.HibernateException;

import net.sf.hibernate.Query;

import net.sf.hibernate.Session;

import net.sf.hibernate.SessionFactory;

import net.sf.hibernate.Transaction;

import net.sf.hibernate.cfg.Configuration;

public class ClientEx2Bis {

public static void main (String argv[]) throws HibernateException {

FacadeEx2Bis facadeEx2 = new FacadeEx2Bis();

try {

facadeEx2.configure();

}catch (HibernateException he1) {

System.out.println("Error Hibernate "+he1.getMessage()+"\n");

he1.printStackTrace();

}

TriString[] triStr=facadeEx2.utilsConnectesHQL();

// affichage des resultats dans la console :

System.out.println("Personne.nom    | Personne.prenom | Utilisateur2.login | nbConnexions");

for (int i=0;i<triStr.length;i++) {

System.out.println(triStr[i].string1+" | "+triStr[i].string2+" | "

+triStr[i].string3+" | "+triStr[i].cpt.toString());

}

// Utilisation API CRITERIA :

facadeEx2.utilsConnectesCriteria();

}

}

Le lancement du client lanceEx2Bis.ksh donne le résultat suivant dans la console :

Nom personne | prenom

EXTERNE0 | JEAN-LOUIS0

EXTERNE0 | JEAN-LOUIS0

EXTERNE0 | JEAN-LOUIS0

EXTERNE0 | JEAN-LOUIS0

EXTERNE0 | JEAN-LOUIS0

EXTERNE0 | JEAN-LOUIS0

EXTERNE0 | JEAN-LOUIS0

Correspondant aux sept connexions de la personne 5 (4 sous Utilisateur2 : 18 et 3 sous Utilisateur2 : 17).
À compléter pour les plus courageux. Je resterai pour ma part, sur les requêtes HQL, qui me semblent plus souples.

III-C-6 - Pour aller plus loin avec Hibernate

Pour information, il semble que la norme EJB V3, fasse un virage important sur les sessions et Entity Beans qui deviendraient des POJO (suppression des homes interfaces, des fichiers de descriptions de déploiement…). La compatibilité ascendante serait assurée. Les tests de EJB V3 seraient faits à partir de deux logiciels de mapping O/R (Hibernate et TopLink) mais pas JDO !
Gavin King fait partie du groupe d'expert EJB V3…
Voir sur le thread http://www.theserverside.com/discussions/thread.tss?thread_id=25804#121153
Çà « bastonne » dur !
Nous avons au cours des exemples précédents effleuré Hibernate, nous avons laissé de côté les concepts suivants :

- aspect performance : avec les lazy initialisation (initialisation paresseuse => seulement quand on en a besoin) et les proxy ;

- utilisation et optimisation des caches ;

- utilisation des outils type XDoclet, MiddleGen ou AndroMDA ;

- Optimistic concurrency control (Versionning, timestamp…) et pessimist Locking

- et sûrement plein d'autres fonctionnalités.

Pour approfondir, lire la Documentation de référence, les FAQ et forum sous http://www.hibernate.org/.
tester et adopter !.

Pour le mois août 2004, est annoncée la sortie de :

HIBERNATE IN ACTION

August 2004, Softbound, 400 pages
ISBN 193239415X
Our price : $44.95
You can order this book from your bookstore by using the ISBN and title listed above.

Et voir aussi ce que va donner EJB V3 et la « guerre » EJB V3 (Hibernate/TopLink) vs JDO V2.0.

III-D - Partie Java Server Faces

III-D-1 - Exemple simplifié

Nous allons repartir de l'exemple correspondant à la structure de la table l'utilisateur telle que définie ci-dessous dans la base Postgres.

essai=> \d utilisateurs

Table «public.utilisateurs»

Colonne    |            Type             | Modificateurs

---------------+-----------------------------+---------------

iduser        | bigint                      | not null

nom           | character varying(40)       | not null

prenom        | character varying(40)       | not null

password      | character varying(20)       | not null

datecm        | timestamp without time zone |

issupprimable | integer                     |

Index :

«cons_iduser» clé primaire, btree (iduser)

Contraintes :

«utilisateurs_issupprimable» CHECK (issupprimable = 0 OR issupprimable = 1)

Ci-dessous le schéma rappelle l'exemple que l'on va traiter.

Les enregistrements de la table utilisateurs :

essai=> select * from utilisateurs;

iduser |    nom    | prenom | password  |           datecm           | issupprimable

--------+-----------+--------+-----------+----------------------------+---------------

1 | admin     | admin  | admin     | 2004-04-28 21:34:26.036093 |             0

25 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 21:54:22.961    |             1

26 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:03:03.054    |             1

27 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:12:16.629    |             1

29 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:49:18.387    |             1

30 | Pasturel1 | jl1    | pasturel1 | 2004-05-07 22:51:08.809    |             1

24 | Pasturel1 | jl1    | arheu123  | 2004-05-07 21:53:54.309    |             1

(7 lignes)

essai=>

III-D-1-a - Mise en place de Hibernate et des Java Server Faces dans le bundle Jonas4-Tomcat5

À partir des maigres informations fournies par la documentation de référence de Hibernate, je donne ci-dessous une configuration permettant de faire fonctionner les exemples fournis dans le présent tutoriel.

Pour mémoire ci-dessous la hiérarchie des ClassLoader de Jonas tirée de la documentation de Jonas (jonas.objectweb.org)

The resulting JOnAS class loader hierarchy is as follows:

Nous avons donc plusieurs solutions pour positionner les .jar nécessaires au fonctionnement de l'implémentation de référence des Java Server Faces et de Hibernate. Nous allons essayer de rendre cette intégration (JSF + Hibernate) accessible au niveau le plus haut : c'est-à-dire le System ClassLoader ou Commons ClassLoader du schéma ci-dessus. La hiérarchie utilisée dans notre tutoriel (sans utilisation des EJB) est décrite par la flèche en gras sur la droite du schéma.

Nos applications dans les exemples du tutoriel sont des Web Application (sans EJB) qui utilisent Hibernate comme outil de persistance et mapping O/R.
Nous allons essayer d'utiliser le classloader global de Jonas pour intégrer JSF et Hibernate.
Le premier exemple simple va nous servir de test pour bien positionner les jar (car certains sont dans des versions différentes). On va remonter au plus haut les versions des .jar identiques à celles utilisées par Jonas. Sinon il faudra laisser des .jar au niveau du classLoader de la Web Application (répertoire WEB-INF/lib).

Nous allons donc positionner tous les jar nécessaires à JSF et Hibernate sous $JONAS_BASE/lib/ext.

Fichiers récupérés sous lib de Java Server Faces :

- demo-components.jar ;

- jsf-api.jar ;

- jstl.jar ;

- jsf-impl.jar ;

- standard.jar.

Fichiers récupérés de l'arborescence Hibernate2 :

- hibernate2.jar ;

- cglib-full-2.0.1.jar ;

- dom4j-1.4.jar ;

- ehcache-0.7.jar ;

- log4j-1.2.8.jar ;

- odmg-3.0.jar ;

- xalan-2.4.0.jar ;

- xerces-2.4.0.jar ;

- xml-apis.jar.

Fichier jdbc Driver Postgres :

- pg74.1jdbc3.jar.

Les fichiers jar de l'arborescence Hibernate déjà présents dans Jonas n'ont pas été recopiés, on utilise ceux fournis par Jonas.

Nous positionnons ensuite des fichiers de configuration pour Hibernate (récupérés de l'arborescence Hibernate) sous le répertoire $JONAS_BASE/conf :

- cache.ccf ;

- ehcache.xml ;

- log4j.properties ;

- treecache.xml.

Nota 1 :
Vous noterez que nous n'avons pas mis de fichier de configuration de Hibernate de nom :
hibernate.properties dans ces arborescences. Contrairement aux exemples antérieurs, nous utiliserons l'autre alternative de configuration de Hibernate à travers le fichier <webapps>.cfg.xml. J'ai positionné le fichier <webapps>.cfg.xml sous le répertoire $JONAS_BASE/conf avec une modification mineure de code pour charger cette configuration lors de l'utilisation de Hibernate (voir plus bas).
On pourrait le positionner ailleurs : par exemple sous l'arborescence WEB-INF, en indiquant le chemin de l'arborescence déployée (à savoir $JONAS_BASE/work/jonas/<webapps>/WEB-INF/<wepapps>.cfg.xml).

Nota 2 :

Je n'ai pas réussi à le faire fonctionner en essayant de lire le fichier de configuration .cfg.xml contenu dans l'archive .war. Je suis preneur de tip si quelqu'un trouve.

Si l'équipe de développement Jonas intègre JSF et/ou Hibernate dans une version future de Jonas, il est possible que les fichiers .jar soient positionnés sous le répertoire $JONAS_ROOT/lib directement et que l'équipe Jonas indique dans quel répertoire les fichiers .cfg.xml sont à positionner.

III-D-1-b - Adaptation du package la classe POJOUtilisateur

Nous allons ouvrir une nouvelle arborescence de fichiers pour gérer le packaging pour cette classe POJOUtilisateur.

L'organisation des fichiers nécessaires et suffisants des sources est la suivante :

Racine : fichiers de compilation ant et propriété de Hibernate

- ./ex3/build.xml ;

- ./ex3/build.properties.

Fichiers sources :

- ./ex3/src/utilisateur1/POJOUtilisateur1.java ;

- ./ex3/src/utilisateur1/BKUtilisateur1.java ;

- ./ex3/src/utilisateur1/BDUser.java ;

- ./ex3/src/utilisateur1/POJOUtilisateur1.hbm.xml ;

- ./ex3/src/utilisateur.cfg.xml.

Fichiers web / jsf :

- ./ex3/web/WEB-INF/web.xml ;

- ./ex3/web/WEB-INF/faces-config.xml ;

- ./ex3/web/success.jsp ;

- ./ex3/web/index.jsp ;

- ./ex3/web/saisieUtil.jsp.

Fichiers obtenus après avoir lancé ant :

- ./ex3/build/jsf-utilisateur/WEB-INF/faces-config.xml ;

- ./ex3/build/jsf-utilisateur/WEB-INF/web.xml ;

- ./ex3/build/jsf-utilisateur/saisieUtil.jsp ;

- ./ex3/build/jsf-utilisateur/success.jsp ;

- ./ex3/build/jsf-utilisateur/index.jsp.

La web Application obtenu :

- ./ex3/jsf-utilisateur.war.

Ant crée puis recopie le fichier jsf-utilisateur.war sous $JONAS_BASE/webapps et recopie le fichier utilisateur.cfg.xml sous $JONAS_BASE/conf.

III-D-1-c - Navigation simpliste de l'application

Le schéma ci-dessous montre la navigation simpliste utilisée dans notre application :

Les fichiers jsp, implémentant les Java Server Faces sont décrits ci-dessous et sont situés sous l'arborescence./ex3/src/web.

Le fichier d'accueil index.jsp qui ne fait qu'une redirection :

<!doctype html public "-//w3c//dtd html 4.0 transitional//en">

<html>

<head>

</head>

<body>

<jsp:forward page="/jsf-utilisateur/saisieUtil.jsp" />

</body>

</html>

La page jsp/jsf de saisie d'un utilisateur saisieUtil.jsp :

<html>

<head> <title>Application Demo Utilisateur</title> </head>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<body bgcolor="white">

<f:view>

<P ALIGN=CENTER STYLE="border : 1.00pt solid #000000; padding : 0.05cm">

<BR><BR>

<FONT SIZE=5><B>CREATION D'UN UTILISATEUR</B></FONT>

<BR><BR><BR>

</P>

<h:form id="saisieUtil" >

<p> <h :outputText id="userNomOut" value="Nom utilisateur"/>

<h :inputText id="userNomIn" value="#{BKUtilisateur1.nom}"    />     </p>

<p> <h :outputText id="userPrenomOut" value="Prenom utilisateur"/>

<h :inputText id="userPrenomIn" value="#{BKUtilisateur1.prenom}"    />     </p>

<p> <h :outputText id="userPasswordOut" value="Password Utilisateur"/>

<h :inputSecret id="userPasswordIn" value="#{BKUtilisateur1.password}" />     </p>

<p> <h :outputText id="userIsModifOut" value="Supprimable (1 = oui) :"/>

<h :inputText id="userIsModifIn" value="#{BKUtilisateur1.isSupprimable}"    />     </p>

<h :commandButton id="submit" action="#{BKUtilisateur1.createUser}" value="Submit" />

</h:form>

</f:view>

</body>

</HTML>

Noter les lignes :

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

incluant les bibliothèques de tag nécessaires aux jsf , la bibliothèque constituant le noyau qui sera préfixée par « f » et la bibliothèque de renderer html préfixée par la lettre « h ».
Ensuite la particularité des JSF est l'utilisation d'une Expression Langage (introduit par les JSP v2.0 => mais noter la subtilité d'identification du JSF EL commençant par # alors que le JSP EL commence par $) pour lier une action sur un composant de la page avec une Backing Bean chargé de gérer l'événement lié à l'action.
Dans notre cas le fait d'appuyer sur submit va déclencher la méthode createUser du Backing Bean BKUtilisateur1. Les valeurs saisies dans les champs des éléments sont liées aux attributs du Backing bean à travers l'attribut value du tag inputText et de l'EL correspondante de l'attribut value et permettent donc à l'aide d'un objet Business Delegate (BDUser) d'aller créer l'utilisateur en base.

La page jsp/jsf de congratulation success.jsp :

<HTML>

<HEAD> <title>Bravo pour la Saisie !!</title> </HEAD>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<body bgcolor="white">

<f:view>

<h :form id="responseForm" >

<h2><h :outputText id="result"

value="BRAVO !"/></h2>

<h :commandButton id="back" value="Back" action="#{BKUtilisateur1.nettoieUser}"/>

</h:form>

</f:view>

</body>

</HTML>

Rien de particulier par rapport aux mécanismes déjà décrits, si ce n'est que l'on profile de nettoyer les valeurs du Backing Bean lorsque l'on clique sur la touche « Back » à l'aide de la méthode nettoieUser.

Le Backing Bean BKUtilisateur1 constituant le modèle de la vue est donné ci-dessous :

package utilisateur1;

import javax.faces.component.UIComponent;

import javax.faces.context.FacesContext;

import javax.faces.validator.LongRangeValidator;

import javax.faces.validator.Validator;

import javax.faces.validator.ValidatorException;

public class BKUtilisateur1 {

private String nom="";

private String prenom;

private String password;

private int isSupprimable;

public void setNom (String _nom)

{

nom=_nom;

System.out.println("Nom saisie : "+_nom);

}

public String getNom()

{

return nom;

}

public void setPrenom (String _prenom)

{

prenom=_prenom;

}

public String getPrenom()

{

return prenom;

}

public void setPassword (String _password)

{

password=_password;

}

public String getPassword()

{

return password;

}

public void setIsSupprimable (int _isSup)

{

isSupprimable=_isSup;

}

public int getIsSupprimable()

{

return isSupprimable;

}

public String createUser()

{

BDUser util=new BDUser(nom,prenom,password,isSupprimable);

util.createUser();

return ("success");

}

public String nettoieUser()

{

setNom("");

setPrenom("");

setPassword("");

setIsSupprimable(0);

return ("success");

}

}

On notera que le Backing Bean délègue la création du User en base au Business Delegate BDUser (voir méthode createUser dans le code ci-dessus).

Le fichier web.xml d'une web application classique, si ce n'est qu'il définit le servlet contrôleur des pages JSF, point d'entrée de toute application JSF.

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE web-app PUBLIC

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<display-name>JavaServer Faces Utilisateur Application</display-name>

<description>

JavaServer Faces Utilisateur Application Application

</description>

<context-param>

<param-name>javax.faces.STATE_SAVING_METHOD</param-name>

<param-value>client</param-value>

</context-param>

<context-param>

<param-name>com.sun.faces.validateXml</param-name>

<param-value>true</param-value>

<description>

Set this flag to true if you want the JavaServer Faces

Reference Implementation to validate the XML in your

faces-config.xml resources against the DTD.  Default

value is false.

</description>

</context-param>

<context-param>

<param-name>com.sun.faces.verifyObjects</param-name>

<param-value>false</param-value>

<description>

Set this flag to true if you want the JavaServer Faces

Reference Implementation to verify that all of the application

objects you have configured (components, converters,

renderers, and validators) can be successfully created.

Default value is false.

</description>

</context-param>

<!-- Faces Servlet -->

<servlet>

<servlet-name>Faces Servlet</servlet-name>

<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>

<load-on-startup> 1 </load-on-startup>

</servlet>

<!-- Faces Servlet Mapping -->

<servlet-mapping>

<servlet-name>Faces Servlet</servlet-name>

<url-pattern>/jsf-utilisateur/*</url-pattern>

</servlet-mapping>

<security-constraint>

<!-- This security constraint illustrates how JSP pages

with JavaServer Faces components can be protected from

being accessed without going through the Faces Servlet.

The security constraint ensures that the Faces Servlet will

be used or the pages will not be processed. -->

<display-name>Restrict access to JSP pages</display-name>

<web-resource-collection>

<web-resource-name>

Restrict access to JSP pages

</web-resource-name>

<url-pattern>/saisieUtil.jsp</url-pattern>

<url-pattern>/success.jsp</url-pattern>

</web-resource-collection>

<auth-constraint>

<description>

With no roles defined, no access granted

</description>

</auth-constraint>

</security-constraint>

</web-app>

Noter le servlet contrôleur : javax.faces.webapp.FacesServlet

Et le fichier important faces-config.xml décrivant la navigation entre les différentes pages JSF.

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE faces-config PUBLIC

"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"

"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>

<application>

<locale-config>

<default-locale>en</default-locale>

<supported-locale>de</supported-locale>

<supported-locale>fr</supported-locale>

<supported-locale>es</supported-locale>

</locale-config>

</application>

<navigation-rule>

<description>

Navigation  a partir de la page saisieUtil.jsp

</description>

<from-view-id>/saisieUtil.jsp</from-view-id>

<navigation-case>

<description>

Navigation  a partir de la page saisieUtil.jsp en cas de retour  "success".

</description>

<from-outcome>success</from-outcome>

<to-view-id>/success.jsp</to-view-id>

</navigation-case>

</navigation-rule>

<navigation-rule>

<description>

Navigation  a partir de la page success.jsp

</description>

<from-view-id>/success.jsp</from-view-id>

<navigation-case>

<description>

Navigation  a partir de la page success.jsp en cas de retour  "success".

</description>

<from-outcome>success</from-outcome>

<to-view-id>/saisieUtil.jsp</to-view-id>

</navigation-case>

</navigation-rule>

<managed-bean>

<description>

Le "backing Bean" BKUtilisateur1

</description>

<managed-bean-name>BKUtilisateur1</managed-bean-name>

<managed-bean-class>utilisateur1.BKUtilisateur1</managed-bean-class>

<managed-bean-scope>session</managed-bean-scope>

</managed-bean>

</faces-config>

Description facilement compréhensible des règles de navigation. On peut rajouter des branchements en cas d'échec par exemple et compléter ce modèle. Je laisse au lecteur le soin de faire l'exercice.

III-D-1-d - Les fichiers constituant la partie métier de la web application

Le premier élément en lien direct est le Business Delegate de notre petite application qui est porté par la classe BDUser.java ci-dessous :

package utilisateur1;

import net.sf.hibernate.*;

import java.io.*;

import net.sf.hibernate.cfg.*;

import java.util.zip.*;

public class BDUser

{

private String nom, prenom,password;

private int isSupprimable;

public BDUser (String _nom,String _prenom,String _password,int _isSup)

{

nom=_nom;

prenom=_prenom;

password=_password;

isSupprimable=_isSup;

}

public  void createUser()

{

Transaction tx=null;

Session session=null;

try

{

// Nouveau avec fichier de conf Hibernate utilisateur.cfg.xml

SessionFactory sessionFactory=new Configuration()

.configure(new File(System.getProperty("jonas.base")

+File.separator+"conf"+File.separator+"utilisateur.cfg.xml"))

.buildSessionFactory();

if (sessionFactory == null)

{

System.out.println ("session Factory est);

}

// ouverture de la session

session =sessionFactory.openSession();

// Creation d'un user

tx = session.beginTransaction();

POJOUtilisateur1 util1=new POJOUtilisateur1();

// on peuple l'objet util

// mais pas la clé primaire, qui nous sera retournée par session.save();

util1.setNom(this.nom);

util1.setPrenom(this.prenom);

util1.setPassword(this.password);

util1.setDateCM(new java.util.Date());

util1.setIsSupprimable(this.isSupprimable);

// on recupere la primary key

Long id= (Long) session.save(util1);

tx.commit();

System.out.println("Id sauvegarde = "+id.toString());

}

catch (Exception e) {

try

{

if (tx!=null) tx.rollback();

e.printStackTrace();

}

catch (HibernateException he) {

he.printStackTrace();

}

}

finally {

try

{

session.close();

}

catch (HibernateException he) {

he.printStackTrace();

}

}

}

}

Ce Business Delegate pour utiliser la persistance O/R gérée par Hibernate à travers un fichier de configuration utilisateur.xml.cfg qui est placé sous $JONAS_BASE/conf comme le montre la ligne ci-dessous extraite du code ci-dessus :

SessionFactory sessionFactory=new Configuration()

.configure(new File(System.getProperty("jonas.base")

+File.separator+"conf"+File.separator

+"utilisateur.cfg.xml")).buildSessionFactory();

Le fichier utilisateur.cfg.xml est donné ci-dessous :

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration

PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"

"http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

<session-factory>

<property name="connection.datasource">jdbc_1</property>

<property name="connection.username">pastjl</property>

<property name="connection.password"></property>

<property name="show_sql">true</property>

<property name="dialect">net.sf.hibernate.dialect.PostgreSQLDialect</property>

<!-- Mapping files -->

<mapping resource="utilisateur1/POJOUtilisateur1.hbm.xml"/>

</session-factory>

</hibernate-configuration>

On notera que dans ce fichier de configuration, Hibernate recherche une datasource dont le nom est jdbc_1. Cette datasource est mise à disposition par Jonas. Elle est décrite dans le fichier $JONAS_BASE/conf/PostgreSQL1.properties dont le contenu est donné un peu plus bas.

À noter qu'il faut aussi fournir dans le fichier utilisateur.cfg.xml un nom d'utilisateur de la datasource et un password, Hibernate ne sait pas le lire dans la ressource jdbc_1 bien que ces paramètres soient positionnés.

Puis est défini dans ce fichier de configuration, le fichier de mapping O/R pour la classe POJOUtilisateur1 : POJOUtilisateur1.hbm.xml.

Le fichier Jonas $JONAS_BASE/conf/PostgreSQL1.properties définissant la ressource jdbc_1 :

###################### PostgreSQL DataSource configuration example

#

#####

#  DataSource configuration

#  Replace db_jonas and jonas by appropriate values.

#

datasource.name         jdbc_1

datasource.url          jdbc:postgresql:essai

datasource.classname    org.postgresql.Driver

datasource.username     pastjl

datasource.password

datasource.mapper       rdb.postgres

#####

#  ConnectionManager configuration

#

#  JDBC connection checking level.

#     0 = no special checking

#     1 = check physical connection is still open before reusing it

#     2 = try every connection before reusing it

jdbc.connchecklevel     1

#  Max age for jdbc connections

#     nb of minutes a connection can be kept in the pool

jdbc.connmaxage         30

#  Test statement

jdbc.connteststmt       select 1

Ensuite le fichier de mapping O/R POJOUtilisateur1.hbm.xml :

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>

<class name="utilisateur1.POJOUtilisateur1" table="utilisateurs">

<id name="idUser" type="long" unsaved-value="null" column ="iduser">

<generator class="sequence"/>

</id>

<property name="nom" type="string"/>

<property name="prenom" type="string"/>

<property name="password" type="string"/>

<property name="dateCM" type="timestamp" column="datecm"/>

<property name="isSupprimable" type="integer" column="issupprimable"/>

</class>

</hibernate-mapping>

Déjà expliqué tout cela dans les chapitres précédents.

Et enfin la classe POJOUtilisateur1.java mappée par Hibernate vers la table Postgres utilisateurs :

package utilisateur1;

public class POJOUtilisateur1 {

private long idUser;

private String nom;

private String prenom;

private String password;

private java.util.Date dateCM;

private int isSupprimable;

public void setIdUser (long _id)

{

idUser=_id;

}

public long getIdUser()

{

return idUser;

}

public void setNom (String _nom)

{

nom=_nom;

}

public String getNom()

{

return nom;

}

public void setPrenom (String _prenom)

{

prenom=_prenom;

}

public String getPrenom()

{

return prenom;

}

public void setPassword (String _password)

{

password=_password;

}

public String getPassword()

{

return password;

}

public void setDateCM (java.util.Date _date)

{

dateCM=_date;

}

public java.util.Date getDateCM()

{

return dateCM;

}

public void setIsSupprimable (int _isSup)

{

isSupprimable=_isSup;

}

public int getIsSupprimable()

{

return isSupprimable;

}

}

Cela aussi déjà expliqué plus haut.

Pour info un extrait du fichier $JONAS_BASE/conf/jonas.properties définissant la mise à disposition de la datasource postgres :

...

###################### JOnAS DBM Database service configuration

#

#  Set the name of the implementation class of the dbm service

jonas.service.dbm.class         org.objectweb.jonas.dbm.DataBaseServiceImpl

#  Set the jonas DataSources. This enables the JOnAS server to load

#  the data dources, to load related jdbc drivers, and to register the data

#  sources into JNDI.

#  This property is set with a coma-separated list of Datasource properties

#  file names (without the '.properties' suffix).

#  Ex: Oracle1,InstantDB1 (while the Datasources properties file names are

#                          Oracle1.properties and InstantDB1.properties)

jonas.service.dbm.datasources  PostgreSQL1

...

Jonas cherche un fichier $JONAS_BASE/conf/PostgreSQL1.properties (décrit plus haut) pour mettre à disposition la datasource à travers le mécanisme JNDI.

Noter :
Le fait de pouvoir préciser par des noms différents (sous $JONAS_BASE/conf dans notre cas) les fichiers de configuration avec une syntaxe du type <webapps>.cfg.xml permet donc de déployer plusieurs webapps avec Hibernate sous Jonas.

Et voilà on a fait notre première application Jonas / JSF / Hibernate / Postgres !

IV - L'application en images

IV-A - Construction de l'application

Nous utiliserons pour construire l'application l'outil de gestion des compilations Ant.

Le fichier build.xml décrit les taches nécessaires à la compilation, à la constitution de l'archive jsf-utilisateur.war de la web application et à son déploiement dans le serveur Jonas/Tomcat.

Le fichier build.properties paramètre Ant dans notre contexte :

tomcat.home=/opt/JONAS_4_0_0

jonas.base=/opt/jonas_base

# The following property settings assume that you are using JWSDP 1.3.

# If you are using any other environment, adjust the properties

# accordingly.

# ----- Dependant Properties -----

# The following properties do not have to be configured if you are

# using a JWSDP 1.3 conteneur.

# In a container other than JWSDP 1.3, make sure that you have each

# of these dependancies and the properties point to the correct

# location :

jsf-api.jar=${jonas.base}/lib/ext/jsf-api.jar

jsf-impl.jar=${jonas.base}/lib/ext/jsf-impl.jar

commons-logging.jar=${tomcat.home}/lib/common/jakarta/commons-logging/commons-logging-api.jar

commons-digester.jar=${tomcat.home}/lib/common/jakarta/commons-digester/commons-digester.jar

commons-beanutils.jar=${tomcat.home}/lib/common/jakarta/commons-beanutils/commons-beanutils.jar

commons-collections.jar=${tomcat.home}/lib/common/jakarta/commons-collections/commons-collections.jar

jstl.jar=${jonas.base}/lib/ext/jstl.jar

standard.jar=${jonas.base}/lib/ext/standard.jar

servlet.jar=${tomcat.home}/lib/common/j2ee/servlet/servlet-2_4.jar

jsp.jar=${tomcat.home}/lib/common/j2ee/jsp/jsp-2_0.jar

hibernate2.jar=${jonas.base}/lib/ext/hibernate2.jar

# ----- Build Control Properties -----

# By default, the dependent libraries are included with

# every sample app. Since the libraries come bundled with

# containers like JWSDP 1.3 you may want to use the container's

# libraries instead and not bundle them with your web application.

# To not bundle the dependant libraries with the sample apps

# comment out the following property:

build.standalone=true

Il faut initialiser les paramètres tomcat.home avec la valeur de $JONAS_ROOT et jonas.base avec la valeur de $JONAS_BASE.
On aura réalisé auparavant les copies des .jar nécessaires (de hibernate et JSF) comme indiqué dans le chapitre précédent en adaptant les arborescences dans le fichier build.properties ci-dessus.

Le fichier build.xml est donné ci-dessous :

<project name="utilisateur" default="build.war" basedir=".">

<target name="init" depends="verifyPreconditions">

<tstamp/>

</target>

<property file="build.properties"/>               <!-- this demo local   -->

<!-- Configure the context path for this application -->

<property name="context.path" value="/jsf-utilisateur"/>

<property name="example" value="jsf-utilisateur" />

<property name="build"   value="${basedir}/build" />

<property name="web" value="${basedir}/web" />

<path id="classpath">

<pathelement location="${commons-beanutils.jar}"/>

<pathelement location="${commons-collections.jar}"/>

<pathelement location="${commons-digester.jar}"/>

<pathelement location="${commons-logging.jar}"/>

<pathelement location="${jsf-api.jar}"/>

<pathelement location="${jsf-impl.jar}"/>

<pathelement location="${jstl.jar}"/>

<pathelement location="${build}/${example}/WEB-INF/classes"/>

<pathelement location="${servlet.jar}"/>

<pathelement location="${hibernate2.jar}" />

</path>

<target name="clean" >

<delete dir="${build}" />

<delete dir="${context.path}" />

</target>

<target name="verifyPreconditions">

<antcall target="checkRequiredFile">

<param name="file" value="${servlet.jar}"/>

<param name="fail.message" value="A jar file containing the Servlet 2.3

and JSP1.2 classes is required to compile guessNumber.

Please define the property servlet.jar in your build.properties file

and ensure that the file exists."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${commons-beanutils.jar}"/>

<param name="fail.message" value="${commons-beanutils.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${commons-digester.jar}"/>

<param name="fail.message" value="${commons-digester.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${commons-logging.jar}"/>

<param name="fail.message" value="${commons-logging.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${commons-collections.jar}"/>

<param name="fail.message" value="${commons-collections.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${jsf-api.jar}"/>

<param name="fail.message" value="${jsf-api.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${jsf-impl.jar}"/>

<param name="fail.message" value="${jsf-impl.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${standard.jar}"/>

<param name="fail.message" value="${standard.jar} must exist."/>

</antcall>

<antcall target="checkRequiredFile">

<param name="file" value="${jstl.jar}"/>

<param name="fail.message" value="${jstl.jar} must exist."/>

</antcall>

</target>

<target name="prepare" depends="init"

description="Create build directories.">

<mkdir dir="${build}/${example}" />

<mkdir dir="${build}/${example}/WEB-INF" />

<mkdir dir="${build}/${example}/WEB-INF/classes" />

<mkdir dir="${build}/${example}/WEB-INF/lib" />

</target>

<!-- Executable Targets -->

<target name="build" depends="prepare,deploy.copyJars"

description="Compile Java files and copy static files." >

<javac srcdir="src" destdir="${build}/${example}/WEB-INF/classes">

<include name="**/*.java" />

<classpath refid="classpath"/>

</javac>

<copy todir="${build}/${example}/WEB-INF">

<fileset dir="web/WEB-INF">

<include name="*.xml" />

</fileset>

</copy>

<copy todir="${build}/${example}/WEB-INF/classes/utilisateur1">

<fileset dir="src/utilisateur1">

<include name="*.xml" />

</fileset>

</copy>

<copy todir="${build}/${example}/">

<fileset dir="web">

<include name="*.html" />

<include name="*.gif" />

<include name="*.jpg" />

<include name="*.jsp" />

<include name="*.xml" />

</fileset>

</copy>

</target>

<target name="deploy.copyJars" if="build.standalone">

</target>

<target name="checkRequiredFile">

<available file="${file}" property="requirement.satisfied"/>

<antcall target="checkRequired.fail"/>

</target>

<target name="checkRequired.fail" unless="requirement.satisfied">

<fail message="${fail.message}"/>

</target>

<target name="build.war" depends="build">

<!-- create a war file for distribution -->

<jar jarfile="${example}.war" basedir="${build}/${example}"/>

<copy todir="/opt/jonas_base/webapps" file="${example}.war" />

<copy todir="/opt/jonas_base/conf" file="./src/utilisateur.cfg.xml"/>

<!--delete file="${example}.war" /-->

</target>

</project>

Le fichier build.xml ci-dessus sert à la compilation et au déploiement de la web application. On adaptera donc ce fichier à sa configuration.

Ensuite pour créer l'application et la mettre en place sous le répertoire $JONAS_BASE/webapps de Jonas, il suffit de lancer la commande (sous le même répertoire que build.xml)

ant

IV-B - Déploiement de l'application dans Jonas

Après avoir lancé Postgesql, Jonas et tapé la l'URL http://localhost:9000/jonasAdmin dans votre navigateur préféré.

et ensuite l'écran de proposition de déploiement où l'on voit notre application jsf-utilisateur.war prête à déployer :

Après avoir basculé la web application dans le panneau de droite, cliqué sur Apply puis Confirm, nous avons déployé notre application prête à l'emploi :

On notera aussi dans la partie gauche la ressource database identifiée par PostgreSQL1 qui est donc disponible.

L'application est maintenant prête à être utilisée.

IV-C - Utilisation de l'application

Pour cela il suffit de rentrer l'URL mappée dans le fichier web.xml par rapport au servlet javax.faces.webapp.FacesServlet : http://localhost:9000/jsf-utilisateur.

L'interface est spartiate et mériterait des mises en forme css. Je n'ai pas utilisé ce mécanisme, mais JSF le permet.

État de la base avant la saisie :

essai=# select * from utilisateurs;

iduser |    nom    | prenom | password |           datecm           | issupprimable

--------+-----------+--------+----------+----------------------------+---------------

1 | admin     | admin  | admin    | 2004-04-28 21:34:26.036093 |             0

24 | Pasturel1 | jl1    | arheu123 | 2004-05-07 21:53:54.309    |             1

57 | Pasturel2 | jlp2   | jlp2     | 2004-06-16 18:11:07.03     |             1

58 | Pasturel3 | jlp3   | jlp3     | 2004-06-16 18:11:20.376    |             1

(4lignes)

Saisie d'un utilisateur :

Et après clic sur Submit, la congratulation :

Et après un clic sur Back le retour à l'écran précédent avec nettoyage des champs de saisie :

Et le résultat dans la base de données :

essai=# select * from utilisateurs;

iduser |      nom      |   prenom   | password |           datecm           | issupprimable

--------+---------------+------------+----------+----------------------------+---------------

1 | admin         | admin      | admin    | 2004-04-28 21:34:26.036093 |             0

24 | Pasturel1     | jl1        | arheu123 | 2004-05-07 21:53:54.309    |             1

57 | Pasturel2     | jlp2       | jlp2     | 2004-06-16 18:11:07.03     |             1

58 | Pasturel3     | jlp3       | jlp3     | 2004-06-16 18:11:20.376    |             1

79 | PASTUREL_DEMO | JEAN-LOUIS | jlp1     | 2004-06-22 18:58:51.69     |             1

(5 lignes)

Et les traces Hibernate probatoires :

Hibernate: select nextval ('hibernate_sequence')

Hibernate: insert into utilisateurs (nom, prenom, password, datecm, issupprimable, iduser) values (     , ?, ?, ?, ?)

Id sauvegarde = 79

Remarque : les sauts de séquence sont dus à des essais infructueux et des effacements d'enregistrements…

Le portage vers Jonas 4.1/tomcat 5 ne devrait pas poser de problème, ainsi que l'adaptation au système Windows. Pour ma part j'ai réalisé ce portage sous Windows avec la base Postgres sous cygwin.

Bon courage pour aller plus loin…