EJB-QL, le SQL selon EJB

Chapitres traités   

Dans l'étude précédente, nous avons passé pas mal de temps à prendre connaissance des beans entités. Le gros avantage de ce type de bean c'est qu'il permet de s'occuper automatiquement de la sauvegarde dans une base de données alors que nous travaillons avec des objets. Ainsi, nous sommes en présence d'une base de données objet plutôt qu'une base de données relationnelle.

Par contre, lorsque nous désirons retrouver des informations, nous les retrouvons certes toujours sous la forme d'un objet, ce qui est vraiment super, mais nous sommes systématiquement obligé de passer par la clé primaire, ce qui est cette fois-ci plutôt limitatif. Ce serait plus judicieux de proproser des requêtes de recherche qui soient adaptées à ce que nous désirons. Il serait par exemple souhaitable d'effectuer une recherche par rapport au nom de la personne plutôt que de passer systématiquement par un identifiant qui, très souvent, est un numéro anonyme.

Comment allons nous procéder ?

Depuis longtemps déjà, SQL est le langage de référence pour exécuter des requêtes sur les bases de données relationnelles. Certes performant et standardisé, celui-ci ne correspond plus aux langages de développement orientés objets.

Le mapping Objet / Relationnel a vu le jour, comme nous l'avons vu, au travers des beans entités. Se dotant de dérivés de SQL qui permettent d'exécuter des requêtes sur les objets, ces systèmes sont très vite devenus référents en la matière. C'est le cas d'EJB-QL qui permet d'utiliser directement les beans entités dans les requêtes.

 

Choix du chapitre Qu'est-ce que EJB-QL ?

EJB-QL (Entreprise JavaBean Query Language) est une spécification de langage de requêtes. Intégré au système EJB, ce langage de requêtes présente plusieurs avantages.

  1. Tout d'abord sa portabilité qui lui permet d'être utilisé à l'identique entre les différentes versions du langage SQL. En effet, celui-ci repose sur une abstraction du langage SQL pour être traduit en "vrai" SQL lors de son exécution.
  2. D'autre part, l'EJB-QL permet d'utiliser les objets des beans entités de l'application directement dans les requêtes pour plus de facilité. De même, le fournisseur de persistance traduira ces requêtes en "vrai" SQL lors de l'exécution.

La finalité de ce langage est en quelque sorte identique à celle du gestionnaire de persistance, qui offre, en effet, la possibilité d'effectuer des requêtes de type INSERT, UPDATE ou encore DELETE.

Le schéma abstrait

Afin de travailler directement avec les entités, EJB-QL s'appui sur un schéma abstrait des beans entités. Le schéma abstrait est une représentation interne des entités et de leurs relations. Toutefois, même si ce terme semble complexe à première vue, nous l'assimilons couramment au nom du bean entité.

Avec la spécification EJB 3, nous utilisons généralement les annotations. Nous pouvons spécifier le nom de référence par l'attribut name de l'annotation @Entity :

@Entity(name = "UnePhoto")
public class Photo {

Toutefois, cette attribut name sur l'annotation @Entity, n'est pas absolument indispensable. En effet, par défaut, le nom de la classe du bean entité est utilisé comme nom de référence.

Ainsi si nous pouvons proposer l'écriture suivante :

@Entity
public class Photo {

Le nom de référence devient Photo.

Le terme abstrait distingue ce schéma du schéma physique de la base de données. Dans une base de données relationnelle, le schéma physique correspond à la structure des tables et des colonnes. En EJB-QL, vous travaillez avec les schémas abstraits et non avec des tables de votre schéma physique.

En résumé, il faut retenir qu'il s'agit d'une représentation abstraite des beans entités, utilisé par EJB-QL pour naviguer entre les propriétés persistantes et relationnelles.

 

Choix du chapitre EJB-QL pour EJB 3

Avant de rentrer dans les détails innombrables de ce langage, voici le premier exemple simple qui vous permettra de comprendre plus facilement la suite des explications.

Je me sers toujours du projet d'archivage des photos numériques qui me permet de stocker les fichiers photos dans la même machine que le serveur d'applications. Je rappelle que j'utilise également ce serveur pour enregistrer un certain nombre d'informations relatives aux photos stockées. Ainsi, je propose de rajouter une méthode getPhotos() au bean session EditionPhotoBean qui permet de retourner l'ensemble des photos archivées. Voici donc ce que nous pouvons écrire :

@Stateless
public class EditionPhotoBean implements EditionPhotoLocal {
   @PersistenceContext(unitName = "Photos-ejbPU")
   EntityManager persistance;
   private final String répertoire = "J:/Photos/";      
...
    public List<Photo> getPhotos() {
        String texteRequête = "SELECT photo FROM Photo AS photo";
        Query requête = persistance.createQuery(texteRequête);
        return requête.getResultList();
    }
}

Cette méthode est très courte et utilise trois concepts différents :

  1. Le langage EJB-QL qui interprète le texte de la requête proposée (texteRequête)
  2. Le gestionnaire d'entité (persistance)
  3. L'API Query.

Cette méthode getPhotos() retourne donc l'ensemble des instances du bean entité Photo dans une liste. Celle-ci peut ensuite être parcourue par un foreach. Les détails d'utilisation de ces éléments sont expliqués tout au long de cette partie.

 

Choix du chapitre L'API Query

L'API Query est le point essentiel pour la jonction entre l'application et l'exécution des requêtes EJB-QL. Les méthodes de celles-ci sont regroupées dans l'interface javax.persistence.Query :

public interface Query {
   public List getResultList();
   public Object getSingleResult();
   public int executeUpdate();
   public Query setMaxResults(int max);
   public Query setFirstResults(int index);
   public Query setHint(String argument, Object argument);
   public Query setParameter(String nom, int valeur);
   public Query setParameter(String nom, Date date, TemporalType type);
   public Query setParameter(String nom, Calendar calendrier, TemporalType type);
   public Query setParameter(int position, Object valeur);
   public Query setParameter(int position, Date date, TemporalType type);
   public Query setParameter(int position, Calendar calendrier, TemporalType type);
   public Query setFlushMode(FlushModeType mode);
}

L'intérêt principal de cette API est de pouvoir créer des requêtes dynamiques sous forme de simples chaînes de caractères, et non de manière statique dans le descripteur de déploiement. Il existe différents moyens de récupérer un objet de type Query. Les méthodes correspondantes sont regroupées dans l'interface EntityManager.

public interface EntityManager {

   public Query createQuery(String requêteEJB-QL);
   public Query createNamedQuery(String requêteNommée);
   public Query createNativeQuery(String requêteSQL);
   public Query createNativeQuery(String requêteSQL, Class résultat);
   public Query createNativeQuery(StringrequêteSQL, String résultat);

}

Dans ce chapitre, nous nous intéresserons uniquement à la première méthode createQuery() de cette interface. Nous verrons les autres méthodes, utltérieurement, suivant les chapitres traités.

Revoici l'exemple qui permet de récupérer l'ensemble des photos archivées :

String texteRequête = "SELECT photo FROM Photo AS photo";
Query requête = persistance.createQuery(texteRequête);
List<Photo> résultat = requête.getResultList();
...
for (Photo unePhoto : résultat) {
  System.out.println("Identification : " + unePhoto.getIdentification());
  ...
}  

Nous récupérons ici un objet Query via la méthode createQuery() du gestionnaire d'entités. Il suffit ensuite d'appeler la méthode getResultList() pour exécuter la requête et récupérer ainsi le résultat.

Voici d'ailleurs une description détaillée des différentes méthodes classiques de l'interface Query.

java.util.List getResultList()

Cette méthode exécute la requête spécifiée et retourne l'ensemble des résultats de celle-ci. Elle permet, par exemple, de récupérer un ensemble be beans entités complet, comme nous l'avons montré dans l'exemple précédent. Il est également possible de spécifier certaines propriétés dans la clause SELECT afin de récupérer seulement certaines valeurs de l'entité, sous forme de tableau.

String texteRequête = "SELECT photo.genre, photo.identification FROM Photo AS photo";
Query requête = persistance.createQuery(texteRequête);
List<Object[]> résultat = requête.getResultList();
...
for (Object[] listePropriétés : résultat) {
  System.out.println("Genre : " + (String)listePropriétés[0]);
  System.out.println("Identification : " + (String)listePropriétés[1]);

}  

Dans le cas proposé ci-dessus, la méthode getResultList() retourne une liste de tableau d'objets.
.

java.lang.Object getSingleList()

Cette méthode exécute la requête spécifiée et retourne un unique résultat. Si le nombre est supérieur à 1, une exception NonUniqueResultException est levée. A l'inverse, si aucun résultat n'est trouvé, une exception de type EntityNotFoundExceptoin est levée.

Cette méthode doit être utilisée si vous êtes sûr que la requête ne retourne qu'un unique résultat.
.

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.identification='Mésange bleue'";
Query requête = persistance.createQuery(texteRequête);
Photo résultat = requête.getSingleResult();

Dans cet exemple, la propriété identification est unique. De ce fait, nous sommes sûrs que la méthode getSingleResult() ne peut renvoyer plusieurs résultats.
.

Query getMaxResults(int max) et Query setFirstResults(int index)

Ces deux méthodes définissent respectivement le nombre maximal de résultats et l'index du premier élément à retourner.

Celles-ci permettent de limiter les résultats de la requête. En effet, si la requête tente de retourner plusieurs miliers de lignes, vous risquez de réduire rapidement les performances de votre application. De plus, nous ne travaillons généralement pas avec l'ensemble des résultats d'un seul coup, mais partie par partie.

String texteRequête = "SELECT photo FROM Photo AS photo";
Query requête = persistance.createQuery(texteRequête);
requête.setMaxResults(30);
requête.setFirstResults(10);
List<Photo> résultat = requête.getResultList();
...
for (Photo unePhoto : résultat) {
  System.out.println("Identification : " + unePhoto.getIdentification());
  ...
}  

Dans cet exemple, nous récupérons au maximum 30 résultats à partir du 10° enregistrement.
.

Query setParameter(String nom, Object valeur) et Query setParameter(int index, Object valeur)

Ces deux méthodes assignent la valeur passée en argument respectivement au paramètre nom ou au paramètre placé à l'index index. Le nom du paramètre est défini dans la requête via :nomDuParamètre ou via ?numéro.

Les paramètres indexés sont placés via un point d'interrogation suivi d'un entier, dont la valeur commence par 0, et il peut être placé à plusieurs endroits simultanéments.

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.id=?0"; 

Les paramètres nommés sont placés grâce aux deux points suivis d'un identifiant.
.

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.id=:photoId";

Nous utilisons, bien entendu, les méthodes setParameter() de l'objet Query pour définir les paramètres à utiliser lors de l'exécution de la requête.
.

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.id=?0";
Query requête = persistance.createQuery(texteRequête);
requête = requête.setParameter(0, 1183645734090);

Pour la requête basée sur les paramètres nommés, c'est un code similaire.
.

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.id=:photoId";
Query requête = persistance.createQuery(texteRequête);
requête = requête.setParameter("photoId", 1183645734090);

Il existe également d'autre surcharges de la méthode setParameter(). En voici les détails :

public interface Query {
...
   public Query setParameter(String name, Date date, TemporalType type);
   public Query setParameter(String name, Calendar calendrier, TemporalType type);

   public Query setParameter(int position, Date date, TemporalType type);
   public Query setParameter(int position, Calendar calendrier, TemporalType type);
}

Deux paramètres sont ajoutés, permettant d'y affecter une marque de temps, via Date ou Calendar, ainsi que le type de cette marque, via l'énumération TemporalType (Valeurs possibles : DATE, TIME, TIMESTAMP).

Notez que ces méthodes retourne l'objet Query sur lequel elles travaillent. Cela n'est pas anodin et permet d'appeler ces méthodes en cascade, comme le montre l'exemple suivant :

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.id=:photoId AND photo.genre=?0";
Query requête = persistance.createQuery(texteRequête);
List<Photo> résultat = requête.setParameter("photoId", 1183645734090).setParameter(0, "Animal").getResultList();

Remarque : le principal avantage d'utiliser de telles requêtes "à paramétrer" est de pouvoir créer des requêtes génériques auxquelles le développeur pourra passer en paramètre tout type d'objet.

int executeUpdate()

Nous avons également la possibilité d'exécuter des requêtes UPDATE (modification) ou DELETE (suppression). Pour cela nous utilisons la méthode executeUpdate().

Celle-ci exécute la requête qui doit être de type UPDATE ou DELETE. Elle retourne le nombre d'enregistrements supprimés ou modifiés.
.

String texteRequête = "DELETE FROM Photo photo WHERE photo.identification = '%s%'";
Query requête = persistance.createQuery(texteRequête);
System.out.println(requête.executeUpdate()+" enregistrement(s) supprimé(s)");

Nous supprimons, dans cet exemple, l'ensemble des photos dont l'identification contient un s puis nous affichons le nombre d'enregistrement(s) supprimé(s).
.

Query setHint(String argument, Object argument)

Cette méthode permet d'utiliser des opérations spécifiques au fournisseur de persistance (plugins). Il est possible, par exemple de spécifier un timeout personnalisé avec Hibernate

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.identification = '%s%'";
Query requête = persistance.createQuery(texteRequête);
requête.setHint("org.hibernate.timeout", 300);

Dans cet exemple, le temps maximal d'exécution d'une requête sera de 300 millisecondes.
.

 

Choix du chapitre Le langage EJB-QL 3

Après avoir pris connaissance avec l'API Query, nous nous intéressons ici plus précisément aux requêtes que vous devez soumettre, pour obtenir le résultat souhaité, tout en respectant le langage EJB-QL. Une requête peut remplir une tâche de sélection (SELECT), modification (UPDATE) ou suppression (DELETE). Une requête EJB-QL est similaire à du SQL, avec les clauses suivantes :

  1. SELECT : liste les beans entités et les propriétés retournées par la requête. Soit, vous demandez le bean en entier, soit les propriétés qui vous intéressent.
  2. FROM : définit les beans entités utilisés. Ceux-ci doivent être déclarés via l'expression AS. Avant le AS, vous précisez le nom de la classe du bean entité. Après le AS, vous spécifier le nom de l'objet correspondant.
  3. WHERE : permet d'appliquer des critères de recherche. Il est possible d'y spécifier aussi bien des types Java ayant leur équivalent dans les bases de données (String, Integer, Double...) mais également des beans entités. Dans ce dernier cas, lors du passage en requête native, le critère s'appliquera sur la clé primaire.

Bien sûr d'autres clauses pourront être rajoutées, comme nous allons le voir, mais celles-ci sont quasi systématiquement utilisées pour récupérer des données.
.

L'opérateur "." sert à naviguer entre les propriétés et les relations des beans entités. Par exemple, pour récupérer l'ensemble des photos du genre animal, voici la quelle est la requête à soumettre :

String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.genre='animal'";

Les requêtes SELECT

La requête la plus utilisée est sans doute SELECT, la requête de recherche. L'instruction suivante permet de récupérer l'ensemble des photos :

SELECT photo FROM Photo AS photo

Il existe de nombreuses similitudes avec le langage SQL. Toutefois, il faut bien comprendre que la différence vient du fait qu'EJB-QL est orienté objet. Dans l'exemple précédent, "Photo" correspond au schéma abstrait du bean entité Photo et "photo" correspond à un des objets (liste) qui va être récupéré. Cet objet est souvent considéré comme un alias (comme en SQL).

La clause As est optionnelle, mais cela permet de mieux comprendre la requête. Voici éventuellement, et plus simplement, ce que nous pouvons écrire :

SELECT photo FROM Photo photo

La clause SELECT détermine le type de toutes les valeurs qui vont être retournées. Ainsi, dans cet exemple, il s'agit du type Photo et nous récupérons le bean entité en entier puisque que nous spécifions l'instance de l'objet photo juste à la suite du mot SELECT.

A titre indicatif, nous pouvons également indiquer que la valeur retournée correspond bien à une entité en spécifiant explicitement qu'il s'agit bien d'un objet qui est attendu :

SELECT OBJECT(photo) FROM Photo AS photo

Les exemples précédents travaillent directement avec l'objet Photo, cependant il est parfois pratique de ne récupérer que le genre, l'identification, etc. Avec EJB 3, il est possible de spécifier les propriétés que l'on souhaite récupérer. Il suffit de les spécifier dans la clause SELECT :

SELECT photo.genre, photo.identification FROM Photo AS photo

Lorsque une requête attend plusieurs attributs pour une même entité, le résultat est intégré dans un tableau d'objet (Object[]), et si plusieurs beans sont concernés par cette recherche, c'est toujours la liste qui est utilisée (java.util.List) grâce à la méthode getResultList().

String texteRequête = "SELECT photo.genre, photo.identification FROM Photo AS photo";
Query requête = persistance.createQuery(texteRequête);
List<Object[]> résultat = requête.getResultList();
...
for (Object[] listePropriétés : résultat) {
  System.out.println("Genre : " + (String)listePropriétés[0]);
  System.out.println("Identification : " + (String)listePropriétés[1]);
} 

De façon plus simple, nous avons quelquefois besoin de la valeur d'un seul attribut d'un bean entité. Le résultat est toujours récolté au travers de la méthode getResultList(), mais cette fois-ci, le retour est une liste du type correspondant à l'attribut souhaité. Ainsi, si je désire récupérer l'ensemble des clés primaires de mes photos stockées, qui sont en réalité, le nom des fichiers photos souvegardés, voici ce que je dois écrire :

String texteRequête = "SELECT photo.id FROM Photo AS photo";
Query requête = persistance.createQuery(texteRequête);
List<Long> résultat = requête.getResultList();
...
for (Long fichier : résultat) {
  System.out.println("Fichier photo : " + fichier +".jpg");
} 

Les requêtes DELETE et UPDATE

Ces requêtes vont fournir un ensemble d'opérations pour modifier ou supprimer un bean entité. Leur particularité commune est de n'avoir qu'une seule entité dans la clause FROM. Ainsi, si vous souhaitez supprimer ou modifier des enregistrements de plusieurs beans entités, vous devez effectuer plusieurs requêtes. Voici les deux exemples montrant l'utilisation de ces requêtes :

DELETE FROM Photo AS photo WHERE photo.identification = 'Mésange bleue'
UPDATE Photo AS photo WHERE photo.id = 1183645734090

La clause DELETE d'EJB-QL ne supporte pas la suppression en cascade. En effet, même si la relation est configurée avec CascadeType.REMOVE ou CascadeType.ALL, il faudra tout de même écrire manuellement leur retrait de la base de données.

Dans cette requête DELETE, nous risquons de recevoir des exceptions dans le cas où le bean entité Photo possèderait des relations. Nous avons alors trois possibilités :

  1. Utiliser le gestionnaire de persistance qui applique la suppression en cascade.
  2. Ecrire explicitement toutes les requêtes EJB-QL de suppression dans le bon ordre.
  3. Utiliser les possibilités de la base de données et gérer la suppression en cascade au niveau de celle-ci.

Dans ce dernier cas, il est possible d'utiliser l'expression ON DELETE CASCADE pour, par exemple, les bases de données de type MySQL version 5 et du moteur de stockage InnoBD.

EJB-QL est très utile pour tout ce qui est recherche. Par contreles requêtes DELETE et UPDATE me semble pas très utiles surtout UPDATE d'ailleurs. Le gestionnaire de persistance se comporte très bien avec les mises à jours des beans entités sans passer par EJB-QL. Toutefois, j'utilise quelquefois la requête DELETE, mais jamais UPDATE.

La clause WHERE

Les permissions incluses dans la clause WHERE existent pour la plupart dans une forme similaire à celles que nous utilisont avec SQL. Voici une liste résumant celles qui sont le plus couramment utilisées :

  1. Les littéraux : Vous pouvez choisir une expression littérale pour sélectionner uniquement le ou les éléments qui vous plaisent. Cela se fait par l'intermédiaire de la clause WHERE, comme nous le ferions sur une requête SQL. Pour cela, vous prenez d'une part l'opérateur de comparaison "=" suivi de l'expression littérale qui dans le cas d'une chaîne de caractères doit être spécifiée entre simple quote. Dans le cas où votre chaîne comporte elle-même une apostrophe, vous devez la doubler pour que celle-ci soit interprétée convenablement. A titre d'exemple, voici la requête EJB-QL à écrire dans le cas où je recherche une photo identifiée "Mésange bleue" :

    SELECT photo FROM Photo AS photo WHERE photo.identification = 'Mésange bleue'

    Si votre littéral est de type primitif, vous n'avez pas besoin d'utiliser des simples quotes pour proposer votre valeur. Ainsi dans le cas où vous avez besoin de récupérer une photo particulière, voici ce que je peux écrire :

    SELECT photo FROM Photo AS photo WHERE photo.id = 1183645734090

  2. Les opérateurs dans les expressions : La clause WHERE est souvent composée d'expressions conditionnelles qui permettent de réduire le champ de recherche. Pour cela, un certain nombre d'opérateurs sont à votre disposition pour réaliser votre filtre en conséquence :
    1. Opérateur de navigation (.) - attributs d'entités par exemple, comme photo.identification ou photo.id.
    2. Opérateurs arithmétiques +, -, *, /.

      SELECT photo FROM Photo AS photo WHERE (photo.largeur + 250) > 1350
    3. Opérateurs de comparaison : =, >, >=, <, <=, <>, LIKE, BETWEEN, IN, IS NULL, IS EMPTY, MEMBER OF.
    4. Opérateurs logiques : NOT, AND, OR.

      SELECT photo FROM Photo AS photo WHERE photo.largeur > 1000 AND photo.largeur <= 900
  3. BETWEEN : Opérateur conditionnel permettant de restreindre les résultats suivant un intervalle. Cette clause doit être utilisée uniquement pour des valeurs de type primitif (byte, short, int, long, double, float) ou leurs classes enveloppes (Byte, Short, Integer, etc.). L'exemple suivant sélectionne toutes les photos comprises entre les deux largeurs spécifiées :

    SELECT photo FROM Photo AS photo WHERE photo.largeur BETWEEN 1000 AND 2000

  4. LIKE : Permet de comparer la valeur d'un champ avec le motif spécifié. La requête suivante récupère, par exemple, toutes les photos ayant une identification dont la deuxième lettre est un 'p' et la troisième un 'a' :

    SELECT photo FROM Photo AS photo WHERE photo.identification = '_pa%'

    Le motif se construit avec deux caractères spéciaux. Le premier est "%" et il s'utilise pour un nombre de caractères indéfinis. Le second "_" représente un seul caractère. Au cas où votre chaîne de caractères utiliserait réellement ces deux caractères, vous avez à votre disposition le caractère d'échappement "\".
  5. IN : En correspondance avec une clause WHERE, teste une appartenance à une liste de chaîne de caractères. La requête suivante récupère, par exemple, toutes les photos appartenant aux papillons ou aux oiseaux :

    SELECT photo FROM Photo AS photo WHERE photo.identification IN('Oiseau', 'Papillon')

  6. IS NULL : Teste si une valeur est nulle. Il s'agit de la valeur par défaut définie quand un champ n'a pas encore été enregistré. Nous pouvons, par exemple, récupérer l'ensemble des photos dont l'identification n'est pas encore spécifiée :

    SELECT photo FROM Photo AS photo WHERE photo.identification IS NULL

    Nous pouvons, très souvent dans ce cas là, utiliser l'opérateur de négation. Ainsi, nous pouvons récupérer toutes les photos qui ont été identifiées :

    SELECT photo FROM Photo AS photo WHERE photo.identification IS NOT NULL

  7. MEMBER OF : Teste l'apparence d'une instance à une collection, tout comme la méthode contains() de l'interface java.util.Collection. Par exemple, à partir de la déclaration suivante :

     7 @Entity
     8 public class Utilisateur implements Serializable {
     9     private String nom;
    10     private String prenom;
    11     private String fonction;
    12     private Collection<Photo> photos = new ArrayList<Photo>();
    ...
    24     @OneToMany
    25     public Collection<Photo> getPhotos() { return photos; }
    26     public void setPhotos(Collection<Photo> photos) { this.photos = photos; }    
    27 }
    j'aimerais récupérer l'ensemble des photos qu'un photographe a pris :

    SELECT photographe FROM Utilisateur AS photographe WHERE :photo MEMBER OF photographe.photos

  8. EMPTY : Teste si une collection est vide.

    SELECT photographe FROM Utilisateur AS photographe WHERE photographe.photos IS EMPTY

  9. NOT: Inverse le résultat de la condition. nous pouvons l'utiliser avec les précédents opérateurs (NOT BETWEEN, NOT LIKE, NOT IN, NOT NULL... )

Manipuler les collections avec l'opérateur IN

La majeure partie des relations utilise des collections. Le parcours de leurs entrées est particulièrement intéressante car il n'existe pas dans la logique relationnelle du SQL. Nous avons pour cela l'opérateur IN. Il doit être placé dans la clause FROM, et permet de déclarer un alias pour les entrées de la collection. Par exemple, nous pouvons récupérer l'ensemble des photos prises par les photographes :

SELECT photo FROM Utilisateur AS photographe , IN(photographe.photos) photo

Nous regroupons ainsi tous les éléments contenus dans la collection photographe.photos, dans l'objet photo. De ce fait, la requête regroupe toutes les photos de tous les photographes.

Les identifiants dans la clause FROM sont déclarés de gauche à droite. Lorsqu'un identifiant est déclaré, nous pouvons l'utiliser dans les déclarations suivantes.
.

La requête suivante permet de récupérer l'ensemble des retouches effectuées sur l'ensemble des photos (il faut imaginer, dans ce cas là, quil existe une collections de retouches pour une photo) :

SELECT retouche FROM Utilisateur AS photographe , IN(photographe.photos) photo , IN(photo.retouches) retouche

Une seconde fonction, ELEMENTS, remplit le même rôle, mais se situe dans la clause SELECT. Voici une requête avec un résultat similaire au premier exemple.
.

SELECT ELEMENTS(photographe.photos) FROM Utilisateur AS photographe

La clause DISTINCT

La clause DISTINCT assure que la requête proposée ne retourne aucun duplicata (des doublons ou plus). Dans l'exemple qui suit, je m'assure que dans l'ensemble des photos récupérées, je suis sûr qu'elles sont uniques :

SELECT DISTINCT photo FROM Photo AS photo ORDER BY photo.id DESC

La clause ORDER BY

La clause ORDER BY permet de ranger par ordre les résultats d'une requête, à partir d'un ou plusieurs attributs en utilisant l'ordre alphanumérique puis alphabétique. La requête suivante retourne l'ensemble des photos suivant l'ordre de stockage :

SELECT photo FROM Photo AS photo ORDER BY photo.id ASC
ou
SELECT
photo FROM Photo AS photo ORDER BY photo.id

Le mot clé optionnel ASC signifie que le classement se fait de façon ascendante. Pour l'effectuer dans l'ordre descendante, il faut utiliser DESC (du plus grand au plus petit). Par défaut, c'est ASC qui est appliqué.

L'exemple suivant retourne les photos, avec en premier, les toutes dernières sauvegardées :

SELECT photo FROM Photo AS photo ORDER BY photo.id DESC

Il est bien entendu possible de combiner ces critères. Ainsi, la requête suivante récupère les photos de la plus petite largeur vers la plus grande et pour une largeur données, le tri se fait ensuite sur la hauteur décroissante :

SELECT photo FROM Photo AS photo ORDER BY photo.largeur , photo.hauteur DESC

Les fonctions

EJB-QL dispose d'un certain nombre de fonctions qui permettent de réaliser des traitements sur les chaînes de caractères ou qui effectuent des opérations basiques sur les nombres.

Les fonctions avec la clause WHERE

EJB-QL possède sept fonctions sur les traitements de chaînes de caractères ainsi que trois fonctions relatives aux opérations numériques.


Les fonctions de traitement de chaîne sont les suivantes :

  1. LOWER(String) : Convertit en minuscule la chaîne spécifiée en argument.
  2. UPPER(String) : Convertit en majuscule la chaîne spécifiée en argument.
  3. TRIM([[LEADING | TRAILING | BOTH] [caractèreBlanc] FROM String) : Permet d'éliminer les caractères blancs du début (LEADING), de fin (TRAILING) ou les deux (BOTH). Si vous ne spécifier pas de caractère blanc particulier, c'est le caractère espace qui est pris par défaut.
  4. CONCAT(String, String) : Retourne une chaîne de caractères qui est la concaténation des deux chaînes passées en argument.
  5. LENGTH(String) : Retourne une valeur entière qui correspond à la longueur de la chaîne de caractères passée en argument.
  6. LOCATE(String, String [ , début]) : Indique si le texte de la deuxième chaîne est présente dans la première et retourne une valeur entière qui spécifie sa localisation dans la première chaîne. S'il est présent, début indique à partir de quelle position de la chaîne de caractères la recherche doit débuter. Si la deuxième chaîne n'est pas comprise dans la première, alors la valeur -1 est retournée.
  7. SUBSTRING(String, début, longueur) : Retourne une partie de la chaîne de caractères passée en argument dont la longueur et la position sont également spécifiées par les arguments.

Les paramètres début et longueur sont des entiers (int). Vous pouvez utiliser ces fonctions avec la clause WHERE pour affiner votre recherche.
.

la recherche suivant retourne un ensemble de photos dont le genre possède plus de six caractères et dont l'identification comporte le mot mésange :

SELECT photo FROM Photo AS photo WHERE LENGTH(photo.genre) > 6 AND LOCATE(UPPER(photo.identification), 'MESANGE') > -1


Les fonctions arithmétiques dans EJB-QL doivent être appliquées sur des types primitifs ou sur leurs classes enveloppes équivalentes. Voici les trois fonctions de traitement numérique :

  1. ABS(nombre) : Retourne la valeur absoule du nombre passé en argument (int, float ou double).
  2. SQRT(double) : Retourne la racine carré de la valeur réelle passée en argument
  3. MOD(int, int) : Retourne le reste de la division entière. Par exemple, avec MOD(7, 5), la valeur retournée est 2.
Les fonctions qui retournes des dates et des heures

EJB-QL possède trois fonctions qui retourne la date courante, l'heure courante ou les deux : CURRENT_DATE, CURRENT_TIME et CURRENT_TIMESTAMP. Voici un exemple qui renvoie toutes photos du jour :

SELECT photo FROM Photo AS photo WHERE photo.instantStockage = CURRENT_DATE

Les fonctions d'agrégation

Les fonctions d'agrégation servent à effectuer des opérations sur des ensembles d'éléments.

  1. COUNT() : retourne le nombre d'enregistrement correspondant au critère de recherche.
  2. MIN() : Retourne la valeur la plus basse.
  3. MAX() : retourne la valeur la plus élévée.
  4. SUM() : Retourne la somme des valeurs.
  5. AVG() : Retourne la valeur moyenne.

Par exemple, nous pouvons utiliser la fonction COUNT() pour déterminer le nombre de photos présentes dans le serveur :

SELECT COUNT(photo) FROM Photo AS photo

ou alors le nombre de photos correspondantes aux mésanges :

SELECT COUNT(photo) FROM Photo AS photo WHERE UPPER(photo.identification) LIKE 'MESANGE%'

Il est possible d'utiliser la clause GROUP BY pour appliquer la fonction d'agrégation sur un lot d'enregsitrements.
.

Voici un exemple permettant de retourner le nombre de photo par photographe :

SELECT photographe.nom , COUNT(photographe.photos) AS nombre FROM Utilisateur AS photographe GROUP BY photographe

L'intérêt du GROUP BY dans cette requête est de pouvoir appliquer cette fonction COUNT() sur chaque photographe.
.

La clause HAVING permet de spécifier des critères, comme avec la clause WHERE, sur une colonne générée par une des fonctions d'agrégation.
.

Voici un exemple qui retourne le nom des photographes qui possèdent plus de deux photos stockées dans le serveur :

SELECT photographe.nom , COUNT(photographe.photos) AS nombre FROM Utilisateur AS photographe GROUP BY photographe.nom HAVING nombre > 2

La clause DISTINCT peut également être avantageuse utilisé, avec des fonctions d'agrégation, afin d'éliminer toutes les duplications.
.

SELECT DISTINCT COUNT(photographe.photos) AS nombre FROM Utilisateur AS photographe GROUP BY photographe

Les jointures

Les jointures permettent de manipuler les relations entre les entités. Nous verrons que les fonctionnalités disponibles sont multiples et pourront s'adapter à de nombreux besoins. Pour illustrer cette déclaration, nous présenterons la récupération des retouches associées aux photos correspondantes, ceci de différentes manières. Voici d'abord les deux entités en relation :

@Entity
public class Retouche implements Serializable {
   private long id;
   private int luminosite;
   private int contraste;
   private Photo photo;

   @Id
   @GeneratedValue
   public long getId() { return id; }
   public void setId(long id) { this.id = id; }

   @OneToOne(mappedBy="retouche")
public Photo getPhoto() { return photo; }
public void setPhoto(Photo photo) { this.photo = photo; } public int getLuminosite() { return luminosite; } public void setLuminosite(int luminosite) { this.luminosite = luminosite; } public int getContraste() { return contraste; } public void setContraste(int contraste) { this.contraste = contraste; } }
 8 @Entity
 9 public class Photo implements Serializable {
10    private long id;
11    private Date instantStockage;
12    private String genre;
13    private String identification;
14    private int largeur;
15    private int hauteur;
16    private long poids;    
17    private Retouche retouche;
18 
19    @OneToOne
20    @JoinColumn(name="RETOUCHE_ID", referencedColumnName="ID")
21    public Retouche getRetouche() { return retouche; }
22    public void setRetouche(Retouche retouche) { this.retouche = retouche; }
...
61 }

Et voici les différentes approches que nous pouvons en faire :

  1. En utilisant la relation entre clés primaires et clés étrangères :

    SELECT photo.identification , retouche.contraste FROM Photo AS photo , Retouche AS retouche WHERE photo.retouche.id = retouche.id

  2. En utilisant les propriétés relationnelles :

    SELECT photo.identification , photo.retouche.contraste FROM Photo AS photo

  3. En utilisant une jointure via l'instruction JOIN :

    SELECT photo.identification , retouche.contraste FROM Photo AS photo JOIN photo.retouche AS retouche

Les résultats retournés sont les mêmes pour chacune des requêtes à quelques exceptions près. En effet, le fonctionnement des jointures est subtil. Lorsqu'une entité Photo ne possède pas de relation avec l'entité Retouche, l'enregistrement ne sera pas retourné !

LEFT JOIN

Afin de renvoyer les enregistrements de l'entité Retouche, même s'ils n'ont pas de relation, il faut effectuer une jointure dite "ouverte". Pour cela, nous utilisons les instructions LEFT OUTER JOIN ou RIGHT OUTER JOIN, également existantes en SQL.

En EJB-QL, l'expression LEFT JOIN permet d'inclure systématiquement la première entité dans le résultat de la requête. RIGHT JOIN, quand à elle, ajoute les résultats de la seconde entité.

Remarque : L'instruction OUTER, utilisée en SQL, est devenue optionnelle.
.

L'exemple suivant utilise une jointure ouverte. Les résultats sont alors différents :

SELECT photo.identification , retouche.contraste FROM Photo AS photo LEFT JOIN photo.retouche AS retouche

En effet, la requête a désormais pris en compte toutes les photos prévues même si aucune retouche n'y est associée.

INNER JOIN

Nous avons vu précédemment l'utilisation de l'opérateur IN. La jointure de type INNER provoque le même résultat. Elle est cependant plus familière aux développeurs utilisant couramment SQL. Il suffit d'utiliser le mot clé INNER JOIN pour réaliser ce type de jointure.

SELECT photo FROM Utilisateur AS photographe INNER JOIN photographe.photos photo

La requête précédente retourne l'ensemble des photos créées quel que soit l'utilisateur. Il est toutefois plus simple d'utiliser l'opérateur IN. Voici la correspondance utilisant IN :

SELECT photo FROM Utilisateur AS photographe , IN(photographe.photos) photo

Gestion du polymorphisme

EJB-QL supporte nativement le polymorphisme, c'est-à-dire les requêtes portant sur une hiérarchie d'objets.

L'exécution de la requête suivante retourne tous les enregistrement liés à l'entité Photo.

SELECT photo FROM Photo AS photo

La collection de résultat contient des objets, aussi bien de type Photo, que de type Utilisateur ou Retouche.
.

Sous-requêtes

Les sous-requêtes peuvent être placées dans les clauses WHERE et HAVING. Comme leur équivalent SQL, elles permettent d'imbriquer les requêtes. L'exemple suivant sélectionne les utilisateurs ayant plus de trois photos :

SELECT photographe FROM Utilisateur AS photographe WHERE (SELECT COUNT(photo) FROM Photo As photo GROUP BY photographe) > 3

Nous remarquons l'utilisation de l'objet (l'alias) photographe de la requête principale dans la sous-requête. Un des principaux intérêt est de pouvoir se substituer aux jointures, notamment dans les requêtes de type UPDATE et DELETE.

Utilisez les sous-requêtes lorsque vous ne pouvez pas faire autrement. Nous vous conseillons d'étudier, d'abord, l'utilisation de jointures ou d'instructions telles que IN, ELEMENTS... qui sont d'ailleurs obligatoires dans des systèmes ne prenant pas en compte les sous-requêtes.

ALL, SOME, ANY

Lorsqu'une sous-requête est susceptible de retourner plusieurs lignes, il est alors possible de quantifier le résultat devant être retourné. Pour cela, nous utilisons les expressions :

  1. ALL : retourne true si tous les résultats de la sous-requête vérifient la condition.
  2. SOME : retourne true si au moins un résultat vérifie la condition.
  3. ANY : retourne true si aucun résultat ne vérifie la condition.

FROM Photo AS photo WHERE 0 < ALL (SELECT retouche.contraste FROM photo.retouche AS retouche)
FROM Photo AS photo WHERE 0 = ANY (SELECT retouche.contraste FROM photo.retouche AS retouche)

EXISTS

L'opérateur EXISTS retourne true si le résultat d'une sous-requête obtient au moins une valeur. Au contraire, si aucune valeur n'est trouvée par la sous-requête, la valeur false est retournée.

FROM Photo AS photo WHERE EXIST (SELECT retouche FROM photo.retouche AS retouche WHERE retouche.contraste = 20)

 

Choix du chapitre Projet qui illustre l'utilisation d'EJB-QL

Afin de justifier l'utilisation d'EJB-QL, je vous propose d'illustrer un certain nombre de requêtes au travers du projet d'archivage de photos que j'ai déjà mis en oeuvre lors de l'étude précédente. Je vais profiter de ces nouvelles compétences pour proposer dans mon application Web, d'une part une classification des photos par genre, et d'autre part une recherche de la ou des photos désirées.

Architecture de l'application d'entreprise

Afin de se remémorer les différents éléments qui font partie de l'application d'entreprise, je vous propose l'architecture globale :

Les beans entités

Revoici les beans entités Retouche et Photo que nous avons déjà mis en place dans l'étude précédente. La relation proposée ici entre les deux beans entités est une agrégation.

photos.Retouche.java
package photos;

import javax.persistence.*;
import java.io.Serializable;

@Embeddable
public class Retouche implements Serializable {
   private int luminosite;
   private int contraste;

   public int getLuminosite() { return luminosite; }
   public void setLuminosite(int luminosite) { this.luminosite = luminosite; }

   public int getContraste() { return contraste; }
   public void setContraste(int contraste) { this.contraste = contraste; }
}
photos.Photo.java
package photos;

import java.io.Serializable;
import java.text.DateFormat;
import java.util.Date;
import javax.persistence.*;

@Entity
public class Photo implements Serializable {
   private long id;
   private Date instantStockage;
   private String genre;
   private String identification;
   private int largeur;
   private int hauteur;
   private long poids;    
   private Retouche retouche;

   @Embedded
   public Retouche getRetouche() { return retouche; }
   public void setRetouche(Retouche retouche) { this.retouche = retouche; }

   @Id
   public long getId() { return id; }
   public void setId(long id) { 
       this.id = id;
       setInstantStockage(new Date(id));
   }
   
   @Temporal(TemporalType.TIMESTAMP)
   public Date getInstantStockage() { return instantStockage; }
   public void setInstantStockage(Date instantStockage) { this.instantStockage = instantStockage; }

   @Column(length=15, nullable=false)
   public String getGenre() { return genre; }
   public void setGenre(String genre) { this.genre = genre; }

   @Column(nullable=false)
   public String getIdentification() { return identification; }
   public void setIdentification(String identification) { this.identification = identification; }

   public int getLargeur() { return largeur; }
   public void setLargeur(int largeur) { this.largeur = largeur; }

   public int getHauteur() { return hauteur; }
   public void setHauteur(int hauteur) { this.hauteur = hauteur; }

   public long getPoids() { return poids; }
   public void setPoids(long poids) { this.poids = poids;  }    

   @Transient
   public String getDateFormat() {
      return DateFormat.getDateInstance(DateFormat.FULL).format(instantStockage);
   }
    
   @Transient
   public String getNomFichier() {
      return id+".jpg";
   }
}

Le bean session stateless EditionPhotoBean

Nous allons maintenant rentrer dans le vif du sujet. Effectivement, nous allons nous servir du bean session EditionPhotoBean pour implémenter les différentes requêtes EJB-QL. Nous allons modifier et rajouter des méthodes qui vont délivrer l'ensemble des renseignements nécessaires pour l'application Web.

photos.EditionPhotoLocal.java
.. 
 8 @Local
 9 public interface EditionPhotoLocal {
10     List<Long> liste();
11     BufferedImage getPhoto(String nom, int largeur) throws IOException;
12     Photo rechercher(String nom);
13     void modifier(Photo photo);
14     void supprimer(long id);    
15     List<Photo> getPhotos();
16     List<Photo> getPhotos(String genre);
17     List<String> getGenres();
18     long nombreTotalDePhoto();
19     List<Photo> recherche(String mot);
20 }
photos.EditionPhotoBean.java
.. 
11 @Stateless
12 public class EditionPhotoBean implements EditionPhotoLocal {
13    @PersistenceContext(unitName = "Photos-ejbPU")
14    EntityManager persistance;
15    private final String répertoire = "J:/Photos/";   
16    
17    public List<Long> liste() {
18      String texteRequête = "SELECT photo.id FROM Photo AS photo";
19      Query requête = persistance.createQuery(texteRequête);
20      return requête.getResultList();
21    }
22 
23    public BufferedImage getPhoto(String nom, int largeur) throws IOException {
..
45    }
46 
47    public Photo rechercher(String nom) {
48      long identifiant = Long.parseLong(nom.substring(0, nom.length()-4));
49      return persistance.find(Photo.class, identifiant);
50    }
51 
52    public void modifier(Photo photo) {
53      persistance.merge(photo); 
54    }
55 
56    public void supprimer(long id) {
57      String texteRequête = "DELETE FROM Photo photo WHERE photo.id = :id";
58      Query requête = persistance.createQuery(texteRequête);
59      requête.setParameter("id", id);
60      requête.executeUpdate();      
61      File fichier = new File(répertoire+id+".jpg");
62      fichier.delete();         
63    }
64 
65    public List<Photo> getPhotos() {
66      String texteRequête = "SELECT photo FROM Photo AS photo ORDER BY photo.genre, photo.identification";
67      Query requête = persistance.createQuery(texteRequête);
68      return requête.getResultList();
69    }
70      
71    public List<Photo> getPhotos(String genre) {
72      String texteRequête = "SELECT photo FROM Photo AS photo WHERE photo.genre = :genre ORDER BY photo.identification";
73      Query requête = persistance.createQuery(texteRequête);
74      requête.setParameter("genre", genre);
75      return requête.getResultList();       
76    }   
77     
78    public List<String> getGenres() {
79      String texteRequête = "SELECT DISTINCT photo.genre FROM Photo AS photo";
80      Query requête = persistance.createQuery(texteRequête);
81      return requête.getResultList();       
82    }
83 
84    public long nombreTotalDePhoto() {
85      String texteRequête = "SELECT COUNT(photo) FROM Photo AS photo";
86      Query requête = persistance.createQuery(texteRequête);
87      return (Long)requête.getSingleResult();       
88    }
89 
90    public List<Photo> recherche(String mot) {
91      String texteRequête = "SELECT photo FROM Photo AS photo WHERE UPPER(photo.identification) LIKE :mot";
92      Query requête = persistance.createQuery(texteRequête);
93      String motif = "%"+mot+"%";
94      requête.setParameter("mot", motif.toUpperCase());
95      return requête.getResultList();         
96    }
97 }
  1. Lignes 17 à 21 : List<Long> liste() : Délivre la liste des clés primaires de l'ensemble des photos archivées. Cette liste correspond également au nom des fichiers photos. Pour arriver à produire cette liste, dans ma requête, je demande juste l'attribut id de l'objet photo. Pour avoir la liste, je passe par la méthode getResultList() de la classe Query.
  2. Lignes 47 à 50 : Photo rechercher(nom) : Cette méthode recherche une photo en particulier à partir du nom du fichier complet. Cette fois-ci, je n'utilise pas EJB-QL. En effet, le nom du fichier sert aussi d'identifiant pour la clé primaire. Je passe, tout simplement, par la méthode find() du gestionnaire de persistance.
  3. Lignes 52 à 54 : modifier(photo) : Cette méthode remet à jour la photo sélectionnée. Là aussi, je n'utilise pas EJB-QL. Tout se fait en une seule ligne. La modification des attributs se fait à partir de l'application Web, lorsque nous changeons les valeurs à partir de l'IHM, ce qui est bien plus pratique. Du coup, nous avons juste besoin de lancer la méthode merge() du gestionnaire de persistance pour que la modification soit effective dans la base de données.
  4. Lignes 56 à 63 : supprimer(id) : Contrairement à l'étude précédente, cette fois-ci j'utilise une requête EJB-QL. Cette méthode supprime donc la photo désirée à partir de son numéro d'identification (clé primaire). La requête utilise le paramètre :id que j'identifie au moyen de la méthode setParameter(). Pour finir, c'est la seule méthode qui utilise la méthode executeUpdate().
  5. Lignes 65 à 69 : List<Photo> getPhotos() : Délivre l'ensemble des photos archivées. Ces photos sont toutefois classées d'abord par genre et ensuite par identification.
  6. Lignes 71 à 76 : List<Photo> getPhotos(genre) : Cette méthode délivre aussi un ensemble de photos, mais uniquement pour un genre nommé. Nous retrouvons donc la même osssature de requête avec, toutefois ici, un filtre pour le genre, et un classement uniquement par identification. Du coup, la aussi, j'utilise un paramètre dénommé :genre.
  7. Lignes 78 à 82 : List<String> getGenres() : Cette méthode retourne la liste des genres de photos archivées. Remarquez bien la présence du filtre DISTINCT qui prend ici toute son importance.
  8. Lignes 84 à 88 : long nombreTotalDePhoto() : Cette méthode retourne un long qui indique le nombre de photos archivées. Cela se fait par l'intermédiaire de la fonction COUNT(). Puisque j'ai besoin d'un seul résultat, je passe par la méthode getSingleResult() de la classe Query.
  9. Lignes 90 à 96 : List<Photo> recherche(mot) : Cette méthode, est certainement la plus importante, et la plus sophistiquée. Elle délivre l'ensemble des photos suivant une lettre, un mot, une partie de mot, un ensemble de lettres, un ensemble de mots spécifiés en argument, etc. Pour cela, nous passons par la clause LIKE qui permet de faire une comparaison par rapport au paramètre :mot. Remarquez bien que ce paramètre est en réalité un motif puisque, je le formate à l'aide du caractère '%' devant et derrière le mot récupéré par la méthode. Il faut que ce motif soit construit avant. En effet, vous ne pouvez pas, dans votre requête, à la fois utiliser un paramètre et mettre en place un motif autour de ce paramètre. Remarquez bien également que je mets en majuscule tous les mots qui servent à la comparaison.

Application Web

Dans les sources qui suivent, vous avez toute l'application Web. Nous voyons ainsi comment utiliser ce bean session stateless que nous venons de mettre en oeuvre. Il s'agit d'une structure classique avec la technologie JSF. Je me sers tout simplement de tout ce que nous avons appris dans les études précédentes. Il suffit de revenir sur ces études pour maîtriser les écritures proposées. Je ne ferais aucun commentaire particulier.

faces-config.xml
<?xml version='1.0' encoding='UTF-8'?>

<faces-config version="1.2" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ...">

    <managed-bean>
        <managed-bean-name>photos</managed-bean-name>
        <managed-bean-class>bean.Photos</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
</faces-config>
bean.Photos.java
 1 package bean;
 2 
 3 import java.util.*;
 4 import javax.ejb.EJB;
 5 import javax.faces.component.*;
 6 import javax.faces.event.ValueChangeEvent;
 7 import javax.faces.model.SelectItem;
 8 import photos.*;
 9 
10 public class Photos {
11     private int largeurVignette = 300;
12     @EJB 
13     private EditionPhotoLocal édition;
14     private UIData table;
15     private List<Photo> photos;
16     private SelectItem[] genres;
17     private String genre = "Recherche";
18     private String recherche;
19 
20     public String getRecherche() {
21         return recherche;
22     }
23 
24     public void setRecherche(String recherche) {
25         this.recherche = recherche;
26     }
27     
28     public void executeRecherche(ValueChangeEvent evt) {
29        recherche = (String)evt.getNewValue();
30     }
31 
32     public SelectItem[] getGenres() {
33        List<String> liste = édition.getGenres();
34        genres = new SelectItem[liste.size()+2];
35        genres[0] = new SelectItem("Recherche", "Recherche");
36        genres[1] = new SelectItem("Tous", "Tous");
37        for (int i=2; i<genres.length; i++) 
38           genres[i] = new SelectItem(liste.get(i-2), liste.get(i-2));
39        return genres;
40     }
41 
42     public String getGenre() {
43         return genre;
44     }
45 
46     public void setGenre(String genre) {
47         this.genre = genre;
48     }
49 
50     public void changeGenre(ValueChangeEvent evt) {
51         genre = (String)evt.getNewValue();
52     }
53     
54     public UIData getTable() {
55         return table;
56     }
57 
58     public void setTable(UIData table) {
59         this.table = table;
60     }
61 
62     public int getLargeurVignette() {
63         return largeurVignette;
64     }
65 
66     public void setLargeurVignette(int