Author: fdesbois Date: 2010-06-14 23:50:20 +0200 (Mon, 14 Jun 2010) New Revision: 2013 Url: http://nuiton.org/repositories/revision/topia/2013 Log: Update TopiaQuery documentation Modified: trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java trunk/topia-persistence/src/site/rst/TopiaQuery.rst 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-06-14 14:21:58 UTC (rev 2012) +++ trunk/topia-persistence/src/main/java/org/nuiton/topia/framework/TopiaQuery.java 2010-06-14 21:50:20 UTC (rev 2013) @@ -615,10 +615,17 @@ } /** - * TODO-fdesbois-2010-06-02 : javadoc + * Used to load properties during query execution using FETCH keyword. This + * keyword is used in a JOIN, so the alias is needed to identify properties + * to load. If no mainAlias is defined in the query, all properties will + * automatically have a generated alias to identify them. Also an empty + * SELECT statement will be defined to retrieve the correct entity depends + * on the mainEntity type in the query. Carefull using addFetch, hibernate + * doesn't support more than 3 or 4 join. In this case, you can use {@link + * #addLoad(String...)} or load manually the entities wanted. * - * @param properties - * @return + * @param properties Properties to load during query execution + * @return the TopiaQuery */ public TopiaQuery addFetch(String... properties) { @@ -673,7 +680,7 @@ } /** - * @param where + * @param where Where statement to add * @return TopiaQuery * @deprecated since 2.3.4, use {@link #addWhere(String)} instead */ @@ -712,9 +719,9 @@ } /** - * @param paramName - * @param constraint - * @param paramValue + * @param paramName name of the parameter to add + * @param constraint constraint to use + * @param paramValue value of this parameter * @return TopiaQuery * @deprecated since 2.3.4, use {@link #addWhere(String, Op, Object)} instead */ @@ -771,8 +778,8 @@ } /** - * @param paramName - * @param paramValue + * @param paramName name of the parameter to add + * @param paramValue value of this parameter * @return TopiaQuery * @since 2.3.1 * @deprecated since 2.3.4, use {@link #addEquals(String, Object...)} instead @@ -847,7 +854,7 @@ } /** - * @param properties + * @param properties map of the properties to add * @return TopiaQuery * @deprecated since 2.3.4 use {@link #addEquals(Map)} */ Modified: trunk/topia-persistence/src/site/rst/TopiaQuery.rst =================================================================== --- trunk/topia-persistence/src/site/rst/TopiaQuery.rst 2010-06-14 14:21:58 UTC (rev 2012) +++ trunk/topia-persistence/src/site/rst/TopiaQuery.rst 2010-06-14 21:50:20 UTC (rev 2013) @@ -62,25 +62,22 @@ 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(); +ou Depuis un topiaContext :: + + TopiaContext transaction = rootContext.beginTransaction(); + TopiaQuery query = transaction.createQuery(Boat.class, "B"); + 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"); @@ -95,24 +92,40 @@ TopiaQuery query = boatDAO.createQuery(); // Recherche sur l'immatriculation du navire : immatriculation = 142154 - query.add("immatriculation", 142154); + query.addEquals("immatriculation", 142154); // Recherche toutes les dates de construction < 2006 - query.add("buildYear", Op.LT, 2006); + query.addWhere("buildYear", Op.LT, 2006); // Recherche des navires ayant un nom query.addNotNull("name"); + // depuis 2.3.4 // Recherche des navires n'ayant pas de nom - query.add("name", Op.EQ, null); + query.addNull("name"); // Recherche des navires ayant une date de construction 2003, 2004 ou 2006 - query.add("buildYear", 2003, 2004, 2006); + query.addEquals("buildYear", 2003, 2004, 2006); + // depuis 2.3.4 + // Recherche entre deux dates + TopiaQuery queryContact = contactDAO.createQuery(); + Calendar dateBegin = new GregorianCalendar(2010,2,3); + Calendar dateEnd = new GregorianCalendar(2010,5,6); + queryContact.addBetween("creationDate", dateBegin.getTime(), dateEnd.getTime()); + + // depuis 2.3.4 + // Utilisation d'une sous-requête (les paramètres de la sous-requête seront + // ajoutés automatiquement à la requête principale en gérant les doublons + // (sur la valeur et la clé)). + // Le ? correspond à l'endroit ou sera injecté la requête, attention aux + // parenthèses. + queryContact.addSubQuery("boat IN elements(?)", query); + 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.addEquals(Boat.IMMATRICULATION, 142154); query.addNotNull(Boat.NAME); ... @@ -121,7 +134,7 @@ TopiaContext transaction = rootContext.beginTransaction(); BoatDAO dao = ModelDAOHelper.getBoatDAO(transaction); - dao.createQuery().add(Boat.IMMATRICULATION, 142154).addNotNull(Boat.NAME); + dao.createQuery().addEquals(Boat.IMMATRICULATION, 142154).addNotNull(Boat.NAME); Opérateurs ---------- @@ -146,11 +159,8 @@ ~~~~~~~~~~~~~~~~~~~~~~~ Il est souvent nécessaire d'ajouter une autre entité au FROM de la requête, -pour ce faire, il existe trois méthodes : +pour ce faire, il existe deux 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);) @@ -160,6 +170,22 @@ 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. +Depuis la 2.3.4, deux méthodes ont été rajoutés pour le cas des jointures : + +- addJoin(Class entityClass, String alias, boolean fetch) : utilise un inner + join pour lier une entité à celle de la requête. Concrètement l'opérateur + JOIN Hql sera utilisé : 'FROM Contact C JOIN C.boat' :: + + TopiaContext transaction = rootContext.beginTransaction(); + ContactDAO dao = ModelDAOHelper.getBoatDAO(transaction); + TopiaQuery query = dao.createQuery("C").addJoin("C.boat", null, false); + +- addLeftJoin(Class entityClass, String alias, boolean fetch) : même chose que + le addJoin sauf que l'opérateur LEFT JOIN Hql sera utilisé. + + +Voir le `Chargement des donnees` pour l'utilisation du fetch. + Ajout d'élément au SELECT ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -232,7 +258,7 @@ 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 +utiliser la méthode de base **addWhere(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 @@ -243,7 +269,7 @@ Date beginDate = ... Date endDate = ... TopiaQuery query1 = dao.createQuery("C"). - add("C." + Contact.VALIDATION + " IS NOT NULL OR " + + addWhere("C." + Contact.VALIDATION + " IS NOT NULL OR " + "C." + Contact.CREATION_DATE + " BETWEEN :begin AND :end"). addParam("begin", beginDate).addParam("end", endDate); @@ -261,17 +287,15 @@ // sélection spécifique pour un navire if (immatriculation != null) { - query2.add("C2." + Contact.BOAT + "." + Boat.IMMATRICULATION, + query2.addEquals("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); + query1.addWhere("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()); + query2.addSubQuery("C2." + Contact.CREATION_DATE + " = (?)", query1); La requête sous forme HQL :: @@ -283,10 +307,10 @@ AND C.boat = C2.boat); Attention - Il ne faut pas utiliser la méthode add(String str, Object value) pour + Il ne faut pas utiliser la méthode addEquals(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 + *query1.addWhere("C." + Contact.BOAT + " = C2." + Contact.BOAT)*. + L'appel *query1.addEquals("C." + Contact.BOAT, "C2." + Contact.BOAT)* ne fonctionnera pas comme souhaité. Résultats @@ -380,4 +404,78 @@ Note Les aggrégats renvoient principalement un type Long et non Integer. +Chargement des donnees +---------------------- +Généralement une fois la requête exécuté, la transaction utilisé est directement +fermé (topiaContext.closeContext()). Dans ce cas, il est souvent nécessaire de +charger certaines entités pour éviter les malencontreuses LazyException d'Hibernate. +Plusieurs possibilités s'offrent à vous : + +Chargement automatique +~~~~~~~~~~~~~~~~~~~~~~ + +Hibernate permet de déclarer explicitement que certaines relations doivent se +charger dès la récupération des entités. Il faut pour cela utiliser le tagValue +*lazy* dans le fichier de properties du modèle. Par exemple pour charger +le navire associé à chaque contact récupéré via une requête, il faut préciser :: + + myapp.entity.Contact.attribute.boat.tagvalue.lazy=false + +Ainsi chaque contact récupéré aura automatiquement son navire associé de chargé. + +**Attention** cependant, il ne faut pas abuser du tagvalue *lazy* car sinon Hibernate +risque de charger une bonne partie de votre base de données à chaque fois, ce qui +peut s'avérer extrêment coûteux. Cette utilisation doit être limitée au cas d'une +simple association comme c'est ici le cas, le Boat chargé étant **indispensable** +à l'utilisation du Contact. + +Chargement manuelle +~~~~~~~~~~~~~~~~~~~ + +La TopiaQuery fournit deux méthodes intéressantes pour le chargement manuelle : + + - addLoad(String...) : permet de charger les relations une fois la requête + exécutée. + + - addFetch(String...) : permet de charger les relations directement au moment + de la requête en utilisant une jointure et le mot clé 'FETCH' Hql. + +Le addLoad est pour le moment limité à des relations unitaires (autant que votre +modèle vous le permet, ex : entityA.entityB.entityC) ou à une seule relation +multiple directe ou indirecte (entityA.entitiesB). Si nous prenons le cas du +modèle d'exemple, il est possible de charger le navire associé aux contacts en +utilisant : queryContact.addLoad(Contact.BOAT); + +Dans le cas du addLoad, plusieurs requêtes seront exécutés suivant le nombre +de Contact résultats. Il est dans ce cas plus judicieux d'utiliser le addFetch +qui chargera directement les Boat au moment de la récupération des Contact : +queryContact.addFetch(Contact.BOAT); L'alias peut s'avérer indispensable pour +l'utilisation du addFetch, voir la javadoc pour l'utilisation des arguments. + +Note + Les méthodes de jointures permettent de faire un fetch directement : + addJoin(String property, String alias, boolean fetch); + Voir la partie de la documentation concernant le FROM de la requête. + +Attention + Hibernate ne supporte pas plus de 3 ou 4 jointures suivant leurs complexités ! + Il est donc important de limiter l'utilisation des fetch aux cas simples. + +Que choisir ? +~~~~~~~~~~~~~ + +- Relation unitaire (N-1) obligatoire : utiliser le tagValue *lazy* + +- Requête relativement simple (moins de deux jointures) : utiliser le addFetch + +- Requête complexe : utiliser le addLoad + +- Si addLoad non utilisable (trop de chargement de collections) : effectuer un + chargement manuelle en parcourant les résultats. Pour le chargement d'une + entité simple, il suffit d'utiliser le getter correspondant pour la charger, + tandis que pour une collection, l'appel à la méthode size permet de charger + l'intégralité des éléments. + + +