Author: fdesbois Date: 2010-03-10 15:57:09 +0100 (Wed, 10 Mar 2010) New Revision: 1827 Log: Add TopiaQuery documentation Added: trunk/topia-persistence/src/site/resources/modelForTopiaQuery.png trunk/topia-persistence/src/site/resources/modelForTopiaQuery.zargo trunk/topia-persistence/src/site/rst/TopiaQuery.rst Modified: trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java Modified: trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java =================================================================== --- trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java 2010-03-10 12:06:08 UTC (rev 1826) +++ trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java 2010-03-10 14:57:09 UTC (rev 1827) @@ -297,7 +297,7 @@ * * @param mainEntityClass */ - protected TopiaQuery(Class<? extends TopiaEntity> mainEntityClass) { + public TopiaQuery(Class<? extends TopiaEntity> mainEntityClass) { this(); setFrom(mainEntityClass); } @@ -878,6 +878,7 @@ /** * Helper method for array type. Each value will be separated by a comma. + * * @param array of String * @return a String with values of the array separated by a comma */ Added: trunk/topia-persistence/src/site/resources/modelForTopiaQuery.png =================================================================== (Binary files differ) Property changes on: trunk/topia-persistence/src/site/resources/modelForTopiaQuery.png ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Added: trunk/topia-persistence/src/site/resources/modelForTopiaQuery.zargo =================================================================== (Binary files differ) Property changes on: trunk/topia-persistence/src/site/resources/modelForTopiaQuery.zargo ___________________________________________________________________ Added: svn:mime-type + application/octet-stream Added: trunk/topia-persistence/src/site/rst/TopiaQuery.rst =================================================================== --- trunk/topia-persistence/src/site/rst/TopiaQuery.rst (rev 0) +++ trunk/topia-persistence/src/site/rst/TopiaQuery.rst 2010-03-10 14:57:09 UTC (rev 1827) @@ -0,0 +1,354 @@ +TopiaQuery +========== + +:Author: Florian Desbois +:Contact: topia-devel@list.nuiton.org ou topia-users@list.nuiton.org +:Revision: $Revision$ +:Date: $Date$ + +L'objet TopiaQuery permet de créer plus simplement des requêtes HQL pour éviter +les concaténations complexes lors de l'utilisation de la méthode **find** +du TopiaContext. + +Chacune des parties de la requête sont indépendantes et seront concaténés au +moment de l'exécution. Il est donc possible d'ajouter des éléments à la +requête à n'importe quel moment de sa construction (from, select, where, order, +group, ...). + +La TopiaQuery peut donc être construite à un endroit et exécutée à un autre. +Une même requête peut également être réutilisée pour être passée en sous-requête +ou exécutée plusieurs fois à la suite avec certains légers changement de +paramètres. + +.. contents:: + +Modèle exemple +-------------- + +Ci-dessous un modèle simple utilisé pour les exemples de cette documentation. + +.. image:: modelForTopiaQuery.png + +Instantiation +------------- + +La TopiaQuery nécessite obligatoirement une entité référence pour être exécutée. +Cette entité correspondra à l'élément principal du FROM de la requête et si +nécessaire sera ajouté automatiquement au SELECT. + +Il y a plusieurs façons d'instancier la TopiaQuery : + +Directement en connaissant la classe de l'entité :: + + TopiaQuery query = new TopiaQuery(Boat.class); + +Directement depuis un DAO :: + + TopiaContext transaction = rootContext.beginTransaction(); + BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); + TopiaQuery query = dao.createQuery(); + +L'intérêt de passer par un DAO est de pouvoir par la suite executer la requête +avec ce même DAO. Il est également possible d'utiliser des alias pour l'élément +principal de la requête pour pouvoir plus facilement gérer les cas de jointure. +Il suffit de préciser l'alias au moment de l'instantiation :: + + TopiaQuery query = new TopiaQuery(Boat.class, "E"); + +ou :: + + TopiaContext transaction = rootContext.beginTransaction(); + BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); + TopiaQuery query = dao.createQuery("E"); + +Ajout d'éléments au WHERE +------------------------- + +Les méthodes de base nécessaire concerne l'ajout d'élements dans le WHERE de la +requête. Plusieurs méthodes sont disponibles suivant les besoins pour ajouter +simplement un élément au where :: + + TopiaQuery query = boatDAO.createQuery(); + + // Recherche sur l'immatriculation du navire : immatriculation = 142154 + query.add("immatriculation", 142154); + + // Recherche toutes les dates de construction < 2006 + query.add("buildYear", Op.LT, 2006); + + // Recherche des navires ayant un nom + query.addNotNull("name"); + + // Recherche des navires n'ayant pas de nom + query.add("name", null); + + // Recherche des navires ayant une date de construction 2003, 2004 ou 2006 + query.add("buildYear", Arrays.asList(new Object[] {2003, 2004, 2006})); + +Il est fortement conseillé d'utiliser les constantes des entités pour les noms +de leurs propriétés :: + + query.add(Boat.IMMATRICULATION, 142154); + query.addNotNull(Boat.NAME); + ... + +La TopiaQuery peut être chaînée, les méthodes permettant l'ajout d'éléments +renvoient toutes la même TopiaQuery avec l'élément ajouté :: + + TopiaContext transaction = rootContext.beginTransaction(); + BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); + dao.createQuery().add(Boat.IMMATRICULATION, 142154).addNotNull(Boat.NAME); + +Opérateurs +---------- + +Une enum interne à la TopiaQuery permet de manipuler les opérateurs nécessaires +aux ajouts dans le WHERE : + +- TopiaQuery.Op.EQ : Opérateur = +- TopiaQuery.Op.GT : Opérateur > +- TopiaQuery.Op.GE : Opérateur >= +- TopiaQuery.Op.LIKE : Opérateur LIKE +- TopiaQuery.Op.LT : Opérateur < +- TopiaQuery.Op.LE : Opérateur <= +- TopiaQuery.Op.NOT_NULL : Opérateur IS NOT NULL +- TopiaQuery.Op.NULL : Opérateur IS NULL + +Autres parties de la requête +---------------------------- + +Ajout d'élément au FROM +~~~~~~~~~~~~~~~~~~~~~~~ + +Il est souvent nécessaire d'ajouter une autre entité au FROM de la requête, +pour ce faire, il existe trois méthodes : + +- addFrom(String str) : ajoute l'élément souhaité au FROM + (Ex : addFrom(Contact.class.getName() + " C");) + +- addFrom(Class entityClass) : ajoute une entité au FROM + (Ex : addFrom(Contact.class);) + +- addFrom(Class entityClass, String alias) : ajoute une entité au FROM avec un + alias (Ex : addFrom(Contact.class, "C");) + +C'est généralement la dernière méthode qui sera la plus utilisé, l'utilisation +des alias facilitant grandement les liaisons entre les entités. + +Ajout d'élément au SELECT +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Deux méthodes sont disponibles pour le cas du SELECT, une méthode addSelect +qui se chargera d'ajouter une propriété au SELECT, et une méthode setSelect +qui définira directement quel est le SELECT souhaité. Pour le cas du addSelect, +la méthode gère automatiquement l'entité principale utilisé pour instancier +la requête, il n'est donc pas nécessaire de l'ajouter manuellement. + +TODO : find an example + +Le setSelect quant à lui est utilisé pour les aggregations par exemple ou +pour récupérer des parties précises du résultat :: + + TopiaContext transaction = rootContext.beginTransaction(); + BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); + // On souhaite connaître le nombre de résultats uniquement + TopiaQuery query = dao.createQuery(). + addNotNull(Boat.NAME). + setSelect("COUNT(*)"); + +autre exemple :: + + // On souhaite récupérer uniquement les noms des navires + TopiaQuery query = dao.createQuery(). + addNotNull(Boat.NAME). + setSelect(Boat.NAME); + +Note + Pour l'ajout d'une contrainte sur l'unicité des résultats, vous pouvez + utiliser la méthode addDistinct() qui permet l'ajout du mot clé DISTINCT sur + le SELECT de la requête. + +Ajout d'élément au GROUP BY +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dans certains cas, il peut être nécessaire d'utiliser un GROUP BY +pour les aggregations :: + + TopiaContext transaction = rootContext.beginTransaction(); + ContactDAO dao = ModelDAOHelper.getContactDAO(transaction); + // On souhaite connaître le nombre de contacts par navire + TopiaQuery query = dao.createQuery(). + setSelect("COUNT(*)"). + addGroup(Contact.BOAT); + +Ajout d'élément au ORDER BY +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Il est possible également d'ajouter un ordre aux résultats :: + + TopiaContext transaction = rootContext.beginTransaction(); + BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); + // Tous les noms des navires triés + TopiaQuery query = dao.createQuery(). + addNotNull(Boat.NAME). + setSelect(Boat.NAME). + addOrder(Boat.NAME); + +Une autre méthode est disponible pour l'ordre descendant : addOrderDesc. +Cependant rien ne vous interdit la syntaxe suivante :: + + addOrder(Boat.NAME, Boat.BUILD_YEAR + " desc"); + +Requête complexe +---------------- + +L'intérêt majeur de la TopiaQuery est de pouvoir la manipuler à travers des +méthodes tout en lui ajoutant des éléments suivant certaines conditions. +Il peut être également utile d'utiliser une TopiaQuery comme sous-requête d'une +autre. De plus certains mots clés comme EXISTS ou encore l'utilisation de +méthode HQL ne possèdent pas leurs propres méthodes. Vous pouvez cependant +utiliser la méthode de base **add(String str)** qui permet l'ajout au WHERE +directement (avec ajout automatique des parenthèses). Dans ce cas, il est +souvent nécessaire d'ajouter des paramètres HQL à la requête (:monParam) qui +devront être ajouté à la TopiaQuery en utilisant la méthode +**addParam(String name, Object value)**. Ex :: + + TopiaContext transaction = rootContext.beginTransaction(); + ContactDAO dao = ModelDAOHelper.getContactDAO(transaction); + Date beginDate = ... + Date endDate = ... + TopiaQuery query1 = dao.createQuery("C"). + add("C." + Contact.VALIDATION + " IS NOT NULL OR " + + "C." + Contact.CREATION_DATE + " BETWEEN :begin AND :end"). + addParam("begin", beginDate).addParam("end", endDate); + +La méthode **fullQuery()** permettra de récupérer la requête sous forme +de chaîne pour pouvoir la manipuler comme sous-requête. Il est possible +également de récupérer les paramètres via la méthode **getParams()** pour +les rajouter à la requête global :: + + // suite du code précédent + // On souhaite la date de création la plus récente sur la requête précédente + query1.setSelect("MAX(C." + Contact.CREATION_DATE + ")"); + + // Préparation de la deuxième requête + BoatDAO boatDAO = ModelDAOHelper.getBoatDAO(transaction); + TopiaQuery query2 = boatDAO.createQuery("C2"); + + // sélection spécifique pour un navire + if (immatriculation != null) { + query2.add("C2." + Contact.BOAT + "." + Boat.IMMATRICULATION, + immatriculation); + + // Ajout d'une condition sur le navire dans la première requête + query1.add("C." + Contact.BOAT + " = C2." + Contact.BOAT); + } + + // Utilisation de la première requête comme sous-requête + query2.add("C2." + Contact.CREATION_DATE + " = (" + query1.fullQuery() + ")"); + // Ajout des paramètres nécessaires de la première requête dans la deuxième + query2.addParams(query1.getParams()); + +La requête sous forme HQL :: + + SELECT C2 FROM Contact C2 + WHERE C2.boat.immatriculation = :immatriculation + AND C2.creationDate = (SELECT MAX(C.creationDate) FROM Contact C + WHERE (C.validation IS NOT NULL OR + C.creationDate BETWEEN :begin AND :end) + AND C.boat = C2.boat); + +Attention + Il ne faut pas utiliser la méthode add(String str, Object value) pour + une comparaison entre deux propriétés comme précédemment : + *query1.add("C." + Contact.BOAT + " = C2." + Contact.BOAT)*. + L'appel *query1.add("C." + Contact.BOAT, "C2." + Contact.BOAT)* ne + fonctionnera pas comme souhaité. + +Résultats +--------- + +Plusieurs méthodes sont disponibles pour récupérer les résultats de la requête. +Pour chaque méthode, il est possible de l'appeler avec en paramètre le contexte +topia ou directement si la requête a été instancié avec un DAO qui contient +lui même le contexte. La méthode de base est la méthode execute() qui renvoie +une liste non typé à l'instar de la méthode find(...) du TopiaContext. Il +est cependant possible de récupérer directement un objet, un entier (pour +un aggregat par exemple) ou une chaîne de caractères suivant le select de la +requête. Pour le count très utile dans de nombreux cas, il est mis à disposition +la méthode **executeCount()** qui se chargera de remplacer temporairement le +select par un COUNT(*). L'avantage c'est que votre requête ne perd pas son +SELECT d'origine pour pouvoir être par exemple executé de façon différente par +la suite. + +Limitation des résultats +~~~~~~~~~~~~~~~~~~~~~~~~ + +Il est possible de limiter le nombre de résultats lors de l'exécution pour +optimiser la requête pour une pagination par exemple. Pour ce faire il faut +utiliser les méthodes **setLimit(int start, int end)** et/ou +**setMaxResults(int max)** :: + + // 18 premiers résultats + query.setLimit(0, 17); + // équivalent à + query.setMaxResults(18); + // résultats du 50ème au 60ème + query.setLimit(49, 59); + +Utilisation des DAO +~~~~~~~~~~~~~~~~~~~ + +Les DAO fournissent également quelques méthodes permettant de récupérer plus +facilement les résultats avec le type souhaité : + +- **findByQuery(TopiaQuery query)** : renvoie une entité (un seul résultat) +- **findAllByQuery(TopiaQuery query)** : renvoie une liste d'entités +- **findAllMappedByQuery(TopiaQuery query)** : renvoie une map d'entités avec + pour clé le topiaId de l'entité. +- **findAllMappedByQuery(TopiaQuery, Class keyClass, String keyProperty)** : + renvoie une map d'entités avec pour clé la propriété passée en argument. + +Exemple :: + + TopiaContext transaction = rootContext.beginTransaction(); + BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); + TopiaQuery query = dao.createQuery(); + ... + Map<String, Boat> boatMap = dao.findAllMappedByQuery(query); + // ou + List<Boat> boatList = dao.findAllByQuery(query); + // ou juste le premier résultat + Boat boat = dao.findByQuery(query); + // ou avec pour clé l'immatriculation du navire (unique) + Map<Integer, Boat> boatMapImma = dao.findAllMappedByQuery(query, + Integer.class, Boat.IMMATRICULATION); + +Résultats complexes +~~~~~~~~~~~~~~~~~~~ + +Certains cas de requête peuvent avoir des résultats plus complexes, notamment +lorsqu'il s'agit de propriété de différentes entités ou avec des aggrégats ( +AVG, SUM, COUNT). Dans ce cas il faut utiliser la méthode de base **execute()** +qui renvoie une liste non typée. Lorsqu'il y a plus d'un élément dans le select +la liste renvoyée est une List<Object[]>, le tableau pour chaque ligne +correspondant aux valeurs des résultats. Exemple :: + + TopiaContext transaction = rootContext.beginTransaction(); + ContactDAO dao = ModelDAOHelper.getContactDAO(transaction); + String boatImma = Contact.BOAT + "." + Boat.IMMATRICULATION; + // On souhaite le nombre de contacts par navire + TopiaQuery query = dao.createQuery(). + setSelect(boatImma, "COUNT(*)").addGroup(boatImma); + + List<Object[]> results = query.execute(); + // Parcours des résultats + for (Object[] result : results) { + Integer immatriculation = (Integer)result[0]; + Long count = (Long)result[1]; + } + +Note + Les aggrégats renvoient principalement un type Long et non Integer. + + Property changes on: trunk/topia-persistence/src/site/rst/TopiaQuery.rst ___________________________________________________________________ Added: svn:keywords + Author Date Revision