====== TP lien XML/Relationnel ====== === Rendu === Il faut rendre un zip contenant projet maven et le fichier sql commenté comprenant le codes des déclencheurs, des procédures stockées et des vues. Il s'agit d'une mise à jour du projet précédent. Bien indiquer dans les différents fichiers sources le nom et le numéro des deux membres du binôme. Le rendu devra être effectué avant le 14/11/2010 à 23h30 [[http://spiral.univ-lyon1.fr/entree.asp?id=9137&objet=echangedocs|via spiral]] === Liens === * [[http://download.oracle.com/javase/6/docs/api/|API Java 6]] * [[http://download.oracle.com/docs/cd/E11882_01/appdev.112/e13995/toc.htm|API driver Oracle]] * [[http://www.techonthenet.com/oracle/index.php|Aide mémoire PL/SQL]] * {{:enseignement:bdav:apis-xml.pdf|Transparents sur les APIs XML}} === Remarques === On considère le schéma du [[tp-plsql-jdbc|PL/SQL et JDBC]]. On ajoutera le code de ce TP au projet celui du TP précédent. Les packages des classes ne sont pas toujours indiqués, en particulier si la classe a été auparavant mentionnée avec son package. Packages utilisés: javax.xml.transform, javax.xml.transform.sax, javax.xml.transform.dom, org.xml.sax, org.xml.sax.helpers, java.sql ===== Classe d'accès aux données ===== -> Créer une classe ForumDAO dans le package ''epul.bdav.forum''. Dans cette classe, ajouter un champ de type ''java.sql.Connection'' que l'on initialisera avec un paramètre du constructeur. ===== Extraction de données relationnelles en XML ===== ==== XMLElement ==== La fonction ''XMLElement'' permet de créer un élément XML. L'expression suivante permet de créer un élément nommé ''prix'' contenant un noeud texte dont la valeur est donnée par l'attribut ''prix_vente'' XMLElement(name "prix", prix_vente) On peut en particulier l'utiliser dans une requête: SELECT XMLElement(name "prix", prix_vente) FROM ventes Il est possible de spécifier plusieurs enfants pour un élément en les séparant par des virgules. Il est également possible d'imbriquer les appels à XMLElement afin de construire un morceau de document XML plus complexe. -> Écrire une requête qui pour chaque message génère un élément XML ”message” avec un élément ”contenu” incluant le corps du message et un élément ”date” incluant la date du message. ==== XMLAttributes ==== La fonction ''XMLAttributes'' permet d'ajouter des attributs à un élément. Le nom par défaut utilisé pour chaque attribut XML est le nom de l'attribut SQL dont on a pris la valeur. Il est également possible d'utiliser la notation ''AS un_nom'' afin de changer le nom de l'attribut XML (de manière similaire au nom des colonnes dans un SELECT). L'exemple suivant crée des éléments "''article''" avec un attribut XML "''ident''" donné par l'attribut SQL ''ident'', un attribut XML "''prix''" donné par l'attribut SQL ''prix_vente'' et un contenu texte donné par l'attribut SQL ''description'': XMLElement(name "article", XMLAttributes(ident,prix_vente as "prix"), description) -> Écrire une requête qui pour chaque message génère un élément ”message” avec un attribut ”id”, un attribut ”date”, un attribut ”auteur” ayant pour valeur l'identifiant de l'auteur et enfin le corps du message comme contenu texte. ==== XMLForest ==== La fonction ''XMLForest'' crée pour chacun de ses arguments un élément (dont on peut optionnellement préciser le nom avec un ''AS'') qui contient du texte correspondant à la valeur calculée pour cet argument. Par exemple: XMLElement(name "article", XMLAttributes(ident,prix_vente as "prix"), XMLForest(nom_article as "nom",description) -> Modifier la requête précédente en utilisant cette fonction pour mettre le corps du message dans un élément ”CORPS” et l'email de l'auteur dans un élément ”mailAuteur”. ==== XMLAgg ==== La fonction ''XMLAgg'' est une fonction d'agrégation pour le type XML (donc typiquement à utiliser en conjonction avec un ''GROUP BY''). Son effet est de mettre les unes à la suite des autres les différentes valeurs de l'expression passée en argument. La requête suivante illustre son fonctionnement: SELECT XMLElement(name "departement", XMLAttributes(deptno), XMLAgg(XMLElement(name "employe",ename))) as RESULTAT FROM scott.emp GROUP BY deptno; Modifier la requête précédente pour ajouter à chaque élément message un //élément// "''reponse''" avec comme contenu l'identifiant de la réponse, et cela pour chacune des réponses à ce message (on ne traitera que les messages ayant des réponses). -> Modifier la requête précédente pour ajouter à chaque élément message un élément ”reponse” avec comme contenu l'identifiant de la réponse, et cela pour chacune des réponses à ce message (on ne traitera que les messages ayant des réponses). -> Remplacer la jointure sur les messages par un LEFT OUTER JOIN. Que peut-on remarquer pour les messages n'ayant pas de réponse? ==== Vues ==== -> Créer une vue qui à chaque identifiant de message associe sa représentation XML telle que décrite ci-dessus. -> Créer une vue qui crée un document XML contenant pour chaque salle l'ensemble des ses messages sous la forme donnée précédemment. Modifier les différentes vues pour que le XML produit soit conforme à la DTD fournie au [[tp-xml-xquery-oracle|TP XQuery]] ===== Récupération des données XML en Java ===== Le type Java ''java.sql.SQLXML'' permet de représenter une donnée XML renvoyée dans un attribut du résultat d'une requête JDBC. Le code suivant montre comment récupérer une ''javax.xml.transform.Source'' depuis une résultat d'une requête: ResultSet rs = ... while (rs.next()) { SQLXML xmldata = rs.getSQLXML(...); javax.xml.transform.dom.DOMSource domSource = xmldata.getSource(DOMSource.class); Document document = (Document) domSource.getNode(); // utilisation du document } Pour récupérer une javax.xml.transform.sax.SAXSource, il suffit de remplacer ''DOMSource.class'' par ''SAXSource.class''. -> Dans la class ''ForumDAO'', ajouter une méthode ''getSalleXML'' qui étant donné une salle requête la vue précédemment créée pour renvoyer une ''Source'' qui est une représentation XML du contenu de la salle. ===== Ajout de données XML dans une base relationnelle ===== ==== Numérotation automatique ==== Une séquence oracle est un compteur qui peut être incrémenté à la demande. Il est possible d'utiliser la méthode ''nextval'' pour incrémenter la séquence puis récupérer la valeur. La méthode ''curval'' permet quand à elle de récupérer la valeur courante de la séquence sans l'incrémenter. Le code suivant permet de créer une séquence ''ma_sequence'': CREATE SEQUENCE ma_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; -> Créer les séquences ''seq_membre'', ''seq_salle'' et ''seq_message''. Mettre à jour leur valeur de ''seq_membre'' pour que cette dernière soit supérieure au plus grand identifiant de la table ''membre''. Procéder de la même manière pour les séquences ''seq_salle'' et ''seq_message'' ((en comparant avec les identifiants de ''salle'' et ''message'')). -> Créer un déclencheur sur insertion dans la table ''message'' qui met à jour la valeur de la séquence si on essaie d'insérer un message dont l'identifiant est supérieur à la valeur courante de la séquence. Créer des déclencheurs similaires pour les tables ''salle'' et ''message''. On pourra utiliser l'instruction ''ALTER SEQUENCE'' pour changer cette valeur: declare inc_val integer; dummy integer; -- necessaire pour le select begin inc_val := 12; execute immediate ('ALTER SEQUENCE seq_membre INCREMENT BY '||inc_val); select seq_membre.nextval into dummy from dual; execute immediate ('ALTER SEQUENCE seq_membre INCREMENT BY 1'); end; / -> Ajouter une méthode qui insère un message dans la table message via JDBC: public void insererMessage(int id, int auteur, int salle, Integer parent, Timestamp date_envoi, String titre, String corps) Remarque: ''parent'' peut être ''null''. -> Ajouter une seconde méthode qui utilise la séquence ''seq_message'' pour créer une identifiant pour message à insérer: public int insererMessageNoId(int auteur, int salle, Integer parent, Timestamp date_envoi, String titre, String corps) La méthode reverra l'identifiant en question. On pourra utiliser une requête du type: SELECT ma_sequence.nextval FROM dual -> Créer les méthodes ''insererSalle'', ''insererSalleNoId'', ''insererMembre'', ''insererMembreNoId'' sur le modèle précédent. ==== Insertion de données XML via SAX ==== Relire les {{:enseignement:bdav:apis-xml.pdf|transparents sur les APIs XML}}. -> Dans le package ''epul.bdav.forum'', créer une classe ''ForumImportHandler'' qui étend ''org.xml.sax.helpers.DefaultHandler''. Elle possèdera un champ ''dao'' de type ''ForumDAO'' initialisé via un argument du constructeur. Cette classe sera utilisée en conjonction avec un parser SAX qui appellera des méthodes telles que ''startDocument'', ''startElement'', ''endElement'', etc lors de la lecture d'un fichier XML. Elle devra insérer les données du document dans la base relationnelle. Pour cela il faudra retenir des informations via des champs de la classe. Il faudra en particulier traiter: * les messages/membre/salle sans identifiant * les identifiants de membre non numérique (//i.e.// maintenir un mapping identifiant textuel <-> identifiant numérique le temps de l'import) * les liens implicites message <-> salle (la salle d'un message correspond à l'élément salle qui contient l'élément message traité). Il sera nécessaire de garder des informations de contexte dans des champs, par exemple le texte contenu dans le dernier élément ''titre'' qui a été lu, l'identifiant de la salle courante, etc. Les exceptions ''SQLException'' seront rattrapées et relancées via: try { ... } catch (SQLException ex) { throw new SAXException(ex); } Pour convertir une date XML en Timestamp SQL: new Timestamp(DatatypeFactory.newInstance().newXMLGregorianCalendar(ma_date_xml).toGregorianCalendar().getTimeInMillis()) -> Créer une méthode ''importXML'' dans la classe ''ForumDAO'' prenant en argument une ''javax.xml.transform.Source'' et mettant en oeuvre le code suivant pour utiliser le ''ForumImportHandler'': javax.xml.transform.Transformer copy = javax.xml.transform.TransformerFactory.newInstance().newTransformer(); copy.transform(la_source, new javax.xml.transform.sax.SAXResult(new ForumImportHandler(this)));