4. La couche Web
La couche web est composée de tout le coté html
de l'application, c'est-à-dire les Jsp, les feuilles de style css, le
javascript, ainsi que des contrôles nécessaires à charger
le modèle de chaque page à afficher.
a. JSTL
Le but de la JSTL est de simplifier le travail des auteurs de
page JSP, c'est à dire la personne responsable de la couche
présentation d'une application web J2EE. En effet, un web designer peut
avoir des problèmes pour la conception de pages JSP du fait qu'il est
confronté à un langage de script complexe qu'il ne maîtrise
pas forcément.
La JSTL permet de développer des pages JSP en utilisant
des balises XML, donc avec une syntaxe proche des langages utilisés par
les web designers, et leur permet donc de concevoir des pages dynamiques
complexes sans connaissances du langage Java.
Sun a donc proposé une spécification pour une
librairie de tags standards : la Java Standard Tag Library (JSTL). C'est
à dire qu'il spécifie les bases de cette librairie, mais qu'il
laisse l'implémentation libre (de la même manière que pour
les serveurs J2EE qui sont des implémentations de la
spécification J2EE).
La JSTL se base sur l'utilisation des Expressions Languages
(EL) en remplacement des scriptlets Java. L'exemple suivant tiré de la
page de commandes permet de se rendre compte de la puissance des Expressions
Languages :
<c:forEach var="item" varStatus="iter"
items="${facture.pannier.contenu}">
...
<td>${item.portable.stockage.taille}Go
(${item.portable.stockage.rotation}tr/min) </td>
...
</c:forEach>
Tout d'abord, on remarque qu'une EL commence par un « $
'> et que le contenu de l'expression doit se trouver a l'intérieur
d'accolades. La page JSP va chercher un attribut s'appelant "item"
successivement et dans l'ordre dans :
1 l'objet « request > qui représente la
requête transmise par le contrôleur :
request.getAttribute("item")
1 l'objet « session > qui représente la session du
client : session.getAttribute("item")
1 l'objet « application > qui représente le
contexte de l'application web : application.getAttribute("item")
Puisque l'expression fait référence a un
attribut composé, JSTL va chercher un membre de l'objet « item >
qui s'appellerait « portable > (un membre public, ou accessible par un
getter), puis va faire de même en cherchant un « membre '>
stockage du type de l'objet « portable >, puis de même avec
« taille '>. Une fois l'objet d'extrémité trouvé,
la JSP l'affiche en utilisant la méthode « toString > de cet
objet. Ainsi, on aura parcouru toute l'arborescence de l'objet très
simplement, sans surcharge de code.
Dans le code précédent, on peut également
observer l'utilisation d'un tag <c :forEach>.Ce tag fait partie
d'une des quatre taglibs que propose JSTL, la taglib « Core > (les
trois autres étant Format, XML, SQL), et propose de nombreuses actions
de base tels que l'affichage de variables, la création ou la
modification de variables de scope, la gestion d'exceptions, etc ~
Une taglib intéressante que nous avons également
testée est la taglib Format (fmt) qui permet de simplifier la
localisation et le formatage des données.
La première utilisation que nous en avons faite
concerne la gestion des dates, dans notre module de news. En effet, les dates
étant enregistrées dans notre base de données de
façon non formatée, il nous faut les traiter avant leur
affichage. Pour cela, la taglig Format propose deux tags :
<fmt:parseDate value="${news.date}" pattern="yyyy-MM-dd"
var="date"/> <fmt:formatDate value="${date}" dateStyle="full"/>
Le premier permet de construire le modèle d'affichage
de la date, en utilisant un pattern personnalisé. Il prend en
paramètre la date à parser, le pattern à suivre, et le nom
de la variable dans laquelle il placera le résultat.
Le tag « formatDate '> n'as plus alors qu'à
afficher le résultat contenu dans la variable « date >.
Mais la principale utilité de cette taglib
réside dans sa faculté d'internationaliser une application,
c'est-à-dire d'afficher indifféremment tous les messages contenus
sur un site dans différentes langues. Nous avons mis en place ce concept
sur les premières étapes d'une commande.
La JSTL utilise les classes standards de Java pour la gestion
de l'internationalisation. Ainsi la classe java.util.Locale permet de
représenter les spécificités régionales, ainsi que
la classe java.util.ResourceBundle pour accéder aux données des
fichiers de localisation.
Un ResourceBundle permet de gérer un ensemble de
fichier *.properties contenant les ressources localisées. Par exemple
pour gérer les langues françaises et anglaises, nous avons les
fichiers suivants:
1 messages_fr_FR.properties
1 messages_en_US.properties
pannierVide=Il n'y a pas d'items dans votre pannier
quantite=Quantite
prixTTC=Prix TTC
Alors que le fichier « message_en_US.properties »
contient pour sa part :
pannierVide=There is no item in your cart
quantite=Quantity
prixTTC=IAT Price
Dès lors, l'utilisation est très simple puisqu'il
suffit de choisir à un moment quelle est la langue à utiliser et
de placer cette locale dans un objet de la session :
String langue = request.getParameter("langue");
if (langue != null){
if (langue.equals("FR"))
request.getSession().setAttribute("langue","fr_FR");
if (langue.equals("EN"))
request.getSession().setAttribute("langue","en_US");
}
Puis de rajouter dans les jsp concernées les deux lignes
suivantes:
<fmt:setLocale value="${sessionScope.langue}"/>
<fmt:setBundle basename="messages" var="maLocale"
scope="session"/>
Le tag « setLocale ~ permet d'aller chercher dans le
scope session la variable représentant la langue a utiliser, celle que
nous lui avons donné précédemment, et le tag setBundle
spécifie le nom du fichier de base contenant les messages (on ajoute
« basename »+ « locale » pour construire «
messages_fr_FR.properties »).
Il ne suffit plus alors qu'à accéder a nos messages
en utilisant le tag « message » en spécifiant la clé
voulue.
<fmt:message bundle="${maLocale}" key="pannierVide"/>
1 Les fichiers *.properties comportent un ensemble de couples
clef/valeur contenant l'ensemble des messages possibles a afficher. On
accède aux données localisées grâce aux
différentes clefs. Par exemple, le fichier «
messages_fr_FR.properties » contient :
Rapport de Projet J2EE Site de E-Commerce
janv. 8
16
Pour avoir mis en place ce mécanisme, nous nous sommes
rendus compte de la puissance de la JSTL, et notamment de la taglib Format car
en à peine plus d'une demi heure, il est possible de rendre son site
complètement multilingue, ce qui crée une forte valeur
ajoutée à une application.
Il est également possible de créer sa propre
bibliothèque de tags. Nous avons également testé cette
possibilité en créant plusieurs tags que nous avons réunis
dans une customTag. Par exemple, nous avons créé un tag
nommé PromoTag dont le résultat est représenté par
la capture suivante.
La construction d'un tel tag se fait en deux étapes. Tout
d'abord, il faut décrire les paramètres de ce tag dans une
tld.
<tag>
<name>promo</name>
<tag-class>yaz.ecommerce.taglib.PromoTag</tag-class>
<bodycontent>scriptless</bodycontent>
<description>
Utilisé pour afficher une offre spéciale
</description>
<attribute>
<name>image</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>titre</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
On y trouve le nom du tag, le chemin vers la classe qui
correspond au code du tag, un paramètre spécifiant si le tag
accepte ou nom des expression language ainsi que la liste complete des
attributs du tag.
L'écriture du code du tag est très simple, il sufit
de créer une classe héritant de
javax.servlet.jsp.tagext.SimpleTagSupport, contenant un membre par
propriété spécifiée avec ses setters puis de se
servir du l'objet JSPWriter qui représente le flux de sortie de la jsp
pour écrire le code html du tag.
public void doTag() throws
JspException, IOException{ JspWriter writer = getJspContext().getOut();
writer.println ("<div class=\"produit1\">"); writer.println (" <div
class=\"caracteristiques\">"); writer.println (" <div
class=\"photo\"><img
src=\"images/"+image+"\"
alt=\""+altImage+"\"/></div>");
writer.println (" <div class=\"description\">");
writer.println (" <h1>"+titre+"</h1>");
Même si dans notre cas, les tags que nous avons
créés ont plus une visée pédagogique que pratique,
on peut quand même entr'apercevoir de nombreuses utilisations de ce
mécanisme. Par exemple, les taglibs de Struts sont construites de cette
façon.
b. Ajax
De toutes les technologies en vogue en ce moment, Ajax est sans
doute l'une des plus prometteuses. Nous n'avons pas dérogé
à cette mode et avons testé son implémentation dans un
site en java. Puisqu'étant déjà familiers du concept et de
son implémentation en php, la retranscription en java est assez
rapide.
Nous avons placé notre requête après
l'affichage des résultats d'une recherche de produits, afin d'ajouter un
produit au panier.
janv. 8
Rapport de Projet J2EE Site de E-Commerce
18
Dans un premier temps (1), l'utilisateur demande la page
/recherche. Le conteneur de servlet donne la main au contrôleur pour
traiter la demande et renvoie finalement le code html de la page
désiré que le navigateur web affiche au client (2). Alors que
cette première partie de l'échange est terminé,
l'utilisateur va déclencher une fonction javascript qui, via une
requête http sur une url bien précise, constituera la demande
d'ajout d'un item dans son pannier (3). Le serveur traitera cette requête
http et retournera non pas une page à afficher, mais du code javascript
(4) que le navigateur pourra exécuter.
Dans notre cas, quand l'utilisateur clique sur le lien pour
ajouter un objet a son panier, il déclenche en fond une requête
vers l'uri AddItemAjax avec deux paramètres : l'identifiant du produit
et la quantité à ajouter.
function doAjouterPannier(id, qte){
var xhr = getXHR();
if (xhr){
// true : requête asynchrone
xhr.open( "POST",
"./addItemAjax?portable="+id+"&quantite="+qte, true);
xhr.onreadystatechange = function() { if (xhr.readyState ==
4){
eval(xhr.responseText);
}
}
// envoie la requete au serveur xhr.send(null);
}
}
Coté serveur, une fois la requête reçue les
paramètres récupérés, on se contente
d'exécuter les fonctions nécessaires a l'ajout du produit au
panier et on retourne quelques lignes de javascript que le client
exécutera pour signifier a l'utilisateur que sa requête s'est
déroulée avec succès en écrivant dans le flux de
sortie de la servlet.
/* ajoute le portable au panier */
pannier.ajouterProduit(idPortable, quantite);
try{
PrintWriter pw = response.getWriter();
pw.println("var p =
document.getElementById('ajoutPannierResponse"+idPortable+"');");
pw.println("p.innerHTML = '(" + quantite + " Article
ajouté au pannier)'");
}
catch(IOException ex){
ex.printStackTrace();
}
5. La couche service
a. Synchronisation
La couche service est une couche clé de l'application
puisque c'est par elle que transitent les informations entre la couche
contrôleur et la couche d'accès aux données (dao). Son
existence provient d'un constat simple : il est nécessaire de
protéger la couche dao de possibles accès concurrentiels sur la
base de données.
Considérons la situation suivante. Imaginons que nous
possédions dans notre module de news une partie administration qui
permettrait d'éditer le contenu d'une news, ou de la supprimer
complètement. Imaginons maintenant que nous avons deux administrateurs
qui tentent de modifier la même news en même temps. On sent bien
qu'il peut arriver un problème entre le moment oi la première
personne commence l'exécution de la fonction d'édition et le
moment ou l'exécution se termine. Si la news a été
supprimée par le deuxième administrateur entre temps, le
programme va tenter de modifier un objet qui n'existe plus.
Pour résoudre ce problème, on a besoin de
synchroniser l'accès aux méthodes de la couche dao afin de
s'assurer qu'un seul thread les exécute a la fois. Pour cela, les
classes du package dao ne sont instanciées qu'un un seul exemplaire.
C'est ce que l'on appelle des « singletons ». Ces singletons sont
répartis sur plusieurs classes, chacune d'entre elle gérant une
seule table en base de données, ce qui rend possible un tel
découpage.
Puisque pendant le déroulement normal de l'application,
il existe de nombreux threads lancés en même temps et
potentiellement concurrents, il faut trouver un moment oi il n'y a qu'un seul
thread de lancé pour instancier nos objets dao. Cet instant a lieu
pendant la phase d'initialisation de la servlet, lors de l'appel a la
méthode init.
Chacune de nos classes dao est alors instanciée une seule
fois et la référence ainsi créée est stockée
en mémoire dans un objet accessible depuis le « scope application
».
/* construit une hashmap qui va contenir toutes les instances de
la couche service */
HashMap<String,Object> mapService = new
HashMap<String,Object>();
/* met la hashmap dans le scope de l'application */
getServletContext().setAttribute("mapService", mapService);
Rapport de Projet J2EE Site de E-Commerce
janv. 8
Nous mettons ainsi dans cette HashMap tous nos objets de la
couche service dont les méthodes synchronisées permettent
d'accéder a la couche dao.
public class LivraisonServiceImpl
implements ILivraisonService{
private ILivraisonDao dao;
public LivraisonServiceImpl(){}
public synchronized void
ajouterLivraison(Livraison livraison){ dao.ajouterLivraison(livraison);
}
public synchronized ArrayList<Livraison>
getAllLivraisons(Integer id){ return
dao.getAllLivraisons(id);
}
public synchronized Livraison
getDefaultLivraison(Integer id){ return
dao.getDefaultLivraison(id);
}
public ILivraisonDao getDao() {
return dao;
}
public void setDao(ILivraisonDao dao) {
this.dao = dao;
}
}
b. Le framework Spring
Lorsque nous ajoutons des objets dans la HashMap
précédente, ce sont toujours des objets implémentant une
interface contenant le prototype des méthodes proposées.
Considérons la classe LivraisonServiceImpl en la
modifiant comme suit :
public LivraisonServiceImpl(){
dao = new LivraisonDaoImpl();
}
20
Si nous avions écrit ceci, notre classe
LivraisonServiceImpl aurait été dépendante de la
classe LivraisonDaoImpl puisque pour utiliser une autre classe
implémentant l'interface ILivraisonDao, nous aurions du
modifier l'objet a la main et recompiler la classe. Pour remédier
à ce problème, nous avons choisi d'utiliser le framework Spring
notamment pour ses fonctions d'Inversion of Control (Injection de
Dépendance en français).
Spring IoC nous permet de créer une application 3tier
où les couches sont indépendantes des autres, c'est a dire que
changer l'une ne nécessite pas de changer les autres. Cela apporte une
grande souplesse dans l'évolution de l'application.
Avec Spring IoC, la classe LivraisonServiceImpl va
obtenir la référence dont il a besoin sur la couche dao de la
façon suivante :
2.
1. Dès que besoin, nous demandons à Spring IoC de
nous donner une référence vers un objet implémentant
l'interface ILivraisonService.
Spring va alors exploiter un fichier XML de configuration qui lui
indique quelle classe doit être instanciée et comment elle doit
être initialisée.
3. Spring IoC nous rends alors la référence de la
couche ainsi créée.
L'avantage de cette solution est que désormais le nom
des classes instanciant les différentes couches n'est plus codé
en dur mais simplement présent dans un fichier de configuration. Changer
l'implémentation d'une couche induira un changement dans ce fichier de
configuration mais pas dans les classes appelantes.
L'instanciation des beans lors de l'initialisation se fait
dès lors de la façon suivante :
/* charge toutes les instances de la couche service dans la
hashmap */ mapService.put("LivraisonServiceImpl",
(ILivraisonService)new XmlBeanFactory(
new ClassPathResource("spring-config.xml"))
.getBean("livraison-service"));
La configuration de Spring s'effectue dans le fichier
spring-config.xml de la façon suivante :
<bean id="livraison-dao"
class="yaz.ecommerce.dao.LivraisonDaoImpl" init-method="init"
destroy-method="close"/>
<bean id="livraison-service"
class="yaz.ecommerce.service.LivraisonServiceImpl"> <property
name="dao">
<ref local="livraison-dao" />
</property>
</bean>
Le premier bean définit un objet de type
LivraisonDapImpl. C'est l'objet que nous allons passer a comme
référence de la couche dao à la couche service. Les
attributs « init-method » et « destroy-method »
spécifient respectivement la méthode à appeler à la
création du bean et à sa destruction.
Le deuxième bean est celui de notre couche service.
Nous lui donnons la classe à instancier et définissons comment
valoriser le membre « dao » en lui disant de prendre le bean local
créé juste avant, dont l'id est « livraison-dao ». De
cette façon, nous obtenons automatiquement un objet java prêt a
l'emploi dont la configuration est aisément modifiable par simple
modification d'un fichier xml.
6. La couche d'accès aux données
(DAO)
a. La persistance des objets en via Object/Relational
Mapping (ORM)
Pour faire court, la notion d'ORM est le fait d'automatiser la
persistance des objets java dans les tables d'une base de données
relationnelle, ainsi que son contraire, récupérer les
données des tables directement dans des objets java.
Rapport de Projet J2EE Site de E-Commerce
janv. 8
Pour implémenter ce concept, un framework doit être
composé des quatre éléments suivants :
22
( Une api pour effectuer les opérations CRUD (Create,
Retrieve, Update, Delete) de base sur les objets des classes persistantes
( Un langage ou une api pour créer des requêtes qui
se réfèrent aux classes et à leurs
propriétés.
( Un moyen simple de spécifier les
métadonnées pour le mapping
( L'implémentation de concepts tels que l'automatic
dirty checking (mise à jour automatique des tables quand les objets
java correspondants sont modifiés), le lazy loading (chargement
des propriétés des objets uniquement des qu'ils sont
accédés) ainsi que d'autres fonctions d'optimisation.
b. Hibernate
Parmi de nombreuses solutions répondant à cette
attente (EJB, iBatis, JDO, Cayenne, Castor, etc..), nous avons choisi
d'utiliser le framework Hibernate car c'est a l'heure actuelle le framework le
plus puissant, le plus complet, et surtout le plus utilisé dans le monde
professionnel.
Néanmoins, son utilisation est assez complexe
malgré le grand nombre de documentations disponibles sur internet. Dans
notre cas, nous nous sommes contenter d'utiliser les fonctions de bases
nécessaires pour la persistance de nos données, sans nous
préoccuper de toute la problématique concernant les
performances.
Tout d'abord, avant d'utiliser Hibernate, il est
nécessaire de modéliser les données que l'on va vouloir
stocker. Cette étape consiste à construire des objets java dont
les membres seront porteurs des informations à stocker. Chaque membre
doit posséder un getter et un setter.
public class Commande {
private Integer id;
private Date dateCommande;
private Date datePreparation; private Date
dateExpedition; private Integer client;
private Livraison livraison;
private Float port;
private Float tva;
public Commande(){}
/* getters, setters */
}
On peut noter en regardant cette simple entité qu'outre
les types primitifs (ou les wrappers), il est également possible de
mapper des types complexes (des objets de la classe Livraison). De
plus, Hibernate est non intrusif, c'est-à-dire qu'il ne nécessite
pas l'implémentation d'une quelconque interface ou héritage.
La deuxième étape consiste à mapper nos
entités dans les tables de la base de données. Pour cela,
Hibernate utilise deux types de fichiers de configuration.
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
org.hibernate.dialect.PostgreSQLDialect
</property>
<property name="hibernate.connection.driver_class">
org.postgresql.Driver
</property>
<property name="hibernate.connection.url">
jdbc:postgresql://localhost/e-commerce
</property>
<property name="hibernate.connection.username">
Postgres
</property>
<property name="hibernate.connection.password">
Administrateur
</property>
<property name="show_sql">true</property>
<property
name="hibernate.jdbc.batch_size">0</property>
<mapping resource="yaz/ecommerce/entite/commande.hbm.xml"/>
</session-factory>
</hibernate-configuration>
La propriété hibernate.dialect sert
à configurer le dialecte SQL de la base de données. Ce dialecte
va servir à Hibernate pour optimiser certaines parties de
l'exécution en utilisant les propriétés spécifiques
à la base. Cela se révèle très utile pour la
génération de clé primaire et la gestion de concurrence.
Chaque base de données à son dialecte propre.
La propriété
hibernate.connection.driver_class permet de renseigner quel est le
driver jdbc à utiliser. Ce driver est fournit par l'éditeur de la
base de données.
La propriété hibernate.connection.url
renseigne sur l'adresse de la base de données.
Les propriétés
hibernate.connection.username et hibernate.connection.pasword
correspondent aux identifiants du compte avec lequel Hibernate va
accéder à la base de données.
Les deux propriétés suivantes sont optionnelles,
le première permettant d'afficher les requêtes crées par
Hibernate dans la console au moment de l'exécution, et la
deuxième désactive les traitements par batch.
Ensuite, il faut ajouter tous les mappings des entités
dont nous avons besoin grâce aux « balises mapping ressource ».
En général, chaque objet persistant possède son fichier de
mapping.
Le contenu d'un tel fichier pour la classe Commande est le
suivant :
Le premier, hibernate.cfg.xml, regroupe les
paramètres ayant trait à la base de données
elle-même (son type, comment y accéder, etc...).
<hibernate-mapping package="yaz.ecommerce.entite">
<class name="Commande" table="commandes">
<id name="id" type="integer">
<generator class="sequence">
<param name="sequence">commandes_id_seq</param>
</generator>
</id>
<property name="dateCommande" column="date_commande"
type="date" />
<property name="datePreparation" column="date_preparation"
type="date" />
<property name="dateExpedition" column="date_expedition"
type="date" />
<property name="client" type="integer" />
<component name="livraison"
class="yaz.ecommerce.entite.Livraison">
<property name="civilite" column="livraison_civilite"
type="string" />
<property name="nom" column="livraison_nom" type="string"
/>
<property name="prenom" column="livraison_prenom"
type="string" />
<property name="adresse" column="livraison_adresse"
type="string" />
<property name="cp" column="livraison_cp" type="integer"
/>
<property name="ville" column="livraison_ville" type="string"
/>
</component>
<property name="port" type="float" /> <property
name="tva" type="float" /> </class>
</hibernate-mapping>
La balise class définit le mapping de la classe
Commande (l'attribut package sert de raccourci pour le package de
cette classe) et l'attribut « table » donne la table dans la base de
donnée qui va être concernée par cette classe.
On doit ensuite mapper tous les champs de la classe un par un,
avec certaines spécificités.
Les classes mappées doivent déclarer la
clé primaire de la table en base de données. Toutes nos classes
ont ainsi une propriété présentant l'identifiant unique
d'une instance. L'élément <id> sert à
définir le mapping entre cette propriété et la clef
primaire en base. L'élément fils <generator> nomme
une classe Java utilisée pour générer les identifiants
uniques pour les instances des classes persistantes. Certaines applications
peuvent proposer leurs propres implémentations
spécialisées pour le générateur de clés mais
pour notre utilisation, nous nous servons d'une des implémentations
intégrées à Hibernate. Il est bien sûr
également possible d'utiliser des clés composées en
mappant plusieurs attributs de la classe comme propriétés
identifiantes mais nous ne les avons pas utilisées.
Toutes les propriétés simples se mappent
aisément en spécifiant d'un coté le nom de la
propriété dans la classe java et de l'autre le nom de la colonne
dans la base de données.
Pour les propriétés complexes, on utilise
l'élément <component> en lui passant en
paramètre le nom de la propriété java dans l'objet et le
chemin vers la classe correspondant a cet objet. Les paramètres sont
alors mappés de la même façon que pour la classe
englobante.
L'utilisation que nous avons faite d'Hibernate est assez
rudimentaire puisque globalement, tous nos objets sont mappés de cette
façon. Néanmoins, après avoir commencé la lecture
du livre Hibernate in Action, nous nous sommes aperçus que la
meilleure façon de modéliser les classes était
différente de la notre, et qu'elle impliquait de nombreux autres
éléments dans les fichiers de configuration.
24
Ainsi, même si notre mapping est fonctionnel, nous nous
sommes rendu compte sur le tard de ses limites lorsque nous avons eu besoin de
mapper des objets plus complexes qu'une simple classe contenant uniquement des
types simples. Ce fut notamment le cas pour mapper les objets
représentant les commandes, puisque ceux-ci comportaient des tableaux
d'objets complexes représentant le contenu de la commande.
Néanmoins, c'est une fois le mapping effectué que
l'on se rend compte de la puissance d'Hibernate. Pour manipuler les objets
persistants, tout passe par l'objet Hibernate Session.
public void enregistrerCommande(
Integer idClient,
Livraison livraison,
Double tva,
Double port,
ArrayList<PannierItem> listePortables){
Transaction tx = null;
(1) session = sessionFactory.openSession();
Commande commande = new Commande(idClient,
livraison,
25.0f , 19.6f);
try {
(2) tx = session.beginTransaction();
(3) session.save(commande);
for (Iterator it = listePortables.iterator();
it.hasNext();){
CommandeContenu contenu =
new CommandeContenu((PannierItem)it.next());
contenu.setCommande(commande.getId()); session.save(contenu);
}
(4) tx.commit();
}
catch (HibernateException e) {
e.printStackTrace();
if (tx != null &&
tx.isActive())
(5) tx.rollback();
}
finally {
session.close();
sessionFactory.close();
}
}
Tout d'abord, on récupère une session Hibernate
(1) grâce a une fabrique qui analyse les fichiers xml de configuration et
qui retourne un objet prêt a l'emploi. Ensuite, on débute une
transaction (2), qui représente une unité de travail,
c'est-à-dire toutes les instructions à réaliser pour
compléter une tâche. En (3), nous constatons qu'il suffit d'une
simple ligne pour insérer notre objet java dans la base de
données ! En (4), nous flushons la session et terminons notre
unité de travail. Il est remarquable de voir que si une exception s'est
produite pendante la durée de la transaction, l'api Hibernate est
capable d'annuler toutes les modifications qui se sont déroulé
durant cette période (5).
Dès lors, on se rend bien compte des raisons qui ont
menés au succès d'Hibernate par ce simple exemple.
tx = session.beginTransaction();
Commande commande =
(Commande)session.get(yaz.ecommerce.entite.Commande.class,id);
tx.commit();
Un simple appel à la fonction « get » avec en
paramètre la classe de l'objet persistant et la clé primaire de
l'objet en base permet de récupérer une instance persistance
java. De même que si l'on utilise le code suivant durant une session:
tx = session.beginTransaction();
Commande commande =
(Commande)session.get(yaz.ecommerce.entite.Commande.class,id)
Commande.setPort(12f);
tx.commit();
L'api Hibernate va détecter le changement d'une des
propriétés de l'objet commande et va mettre a jour
automatiquement l'enregistrement correspondant en base de données.
En ce qui concerne le requêtage, Hibernate est aussi bien
capable d'utiliser le langage SQL
tx = session.beginTransaction();
Query q = session.createQuery("select * from commandes_contenu
where
commande=:id");
q.setInteger("id",id);
contenu = (ArrayList<CommandeContenu>)q.list();
tx.commit();
Que le langage HQL (Hibernate Query Language). Ce dialecte
orienté objet a été développé au
dessus du langage SQL afin de récupérer des objets
en base de données (le R de CRUD).
tx = session.beginTransaction();
Query q = session.createQuery("from Commande as c where c.client
=
?").setInteger(0, id);
liste = q.list();
tx.commit();
La différence entre les deux précédentes
requêtes est qu'en HQL, la requête retourne une liste non pas
d'Object mais de Commande.
Hibernate propose même un autre type de requêtage,
via l'api Query By Criteria (QBC) qui permet de construire ses
requêtes en spécifiant les contraintes dynamiquement sans
manipulation de chaines de caractères comme on serait obligé de
le faire en HQL ou en SQL.
Criteria crit =
session.createCriteria(yaz.ecommerce.entite.News.class);
crit.addOrder(Order.desc("date"));
crit.setMaxResults(10);
liste = (ArrayList)crit.list();
Rapport de Projet J2EE Site de E-Commerce
janv. 8
De la même façon, pour récupérer un
objet persistant, on utilisera la fonction suivante :
26
|