15 Développement d'application de base de données en Java 21/04/2017 © Robert Godin. Tous droits réservés. 1 Patron d'architecture en couche (layer) Couche présentation Couche contrôle (coordonnateur d'application) Couche domaine d'application (ou métier) Couche de services. – – – – – 21/04/2017 persistance transaction communication sécurité etc. © Robert Godin. Tous droits réservés. 2 15.1 Application Java/JDBC client-serveur BD Réseau Programme d'application Logiciel intermédiaire Logiciel intermédiaire Pilote de télécommunication Pilote de télécommunication Client Programme d'application : - présentation -contrôle -domaine d'application SGBD Serveur SGBD - persistence - partie du domaine d'application ? Client 21/04/2017 © Robert Godin. Tous droits réservés. Serveur 3 Cas d ’utilisation EnregistrerPrêts Commis au prêt Système 1. Le commis invoque l'écran d'enregistrement du prêt 2. Le système demande la saisie de l'identificateur de l'utilisateur 3. Le commis saisit l'identificateur de l'utilisateur à l'aide de la carte de membre ou manuellement 4. Le système affiche le nombre de prêts en cours et demande de saisir l'identificateur de l'exemplaire. 5. Le commis entre l'identificateur de l'exemplaire à l'aide du lecteur optique ou manuellement 6. Le système enregistre le prêt, affiche un message d'acquiescement. 21/04/2017 © Robert Godin. Tous droits réservés. 4 Cas de test pour scénario normal 21/04/2017 © Robert Godin. Tous droits réservés. 5 Cas d ’exception : nombre maximal d'emprunts (ici 2) atteint 21/04/2017 © Robert Godin. Tous droits réservés. 6 Cas d ’exception : exemplaire non disponible 21/04/2017 © Robert Godin. Tous droits réservés. 7 Cas d'utilisation et classes métiers concernées Exemplaire Personne nom : String pré nom : String {UNIQUE: idExemplaire} idExemplaire : String dateAchat : Date statut : enum(prêté, disponible, retiré) 1 Utilisateur 0..* Prêt {UNIQUE :idUtilisateur} idUtilisateur : String 1 motPasse : String EnregistrerPrêts (from PackageCasServicePrêt) catégorieUtilisateur {disjointe, complète} 0..* datePrêt : Date {disjointe, complète} PrêtEnCours 21/04/2017 Empl oyé Membre {UNIQUE : cod eMatricule} codeMatri cul e : String catégori eEm ployé : enum (b ibl io thécaire, co mm is ) téléphoneRésidence : String nbMaxPrêts : Integer = 5 duréeMaxPrêts : Integer = 7 © Robert Godin. Tous droits réservés. 8 Patron courtier BD : classes du programme Java et tables concernées ~ Data Access Object (DAO) + Domain Data Transfer Object Person ne nom : Stri ng préno m : Stri ng Utilisateur idUtilisateur : String motPasse : String catégorie : String lesPrêts : Vector Employé Membre {UNIQUE : codeMatricule} codeMatricule : String catégorieEmployé : enum(bibliothécaire, commis) té léph oneRésidence : String nbMaxPrêts : Integer = 5 duréeMaxPrêts : Integer = 7 Exemplaire idExemplaire : String statut : String PrêtEnCours CourtierBDUtilisateur CourtierBDPrêtsEnCou rs Courtie rBDExem plaire ch erch erVari ables Stati quesDeMem bre() ch erch erUtili sateu rParIdUti lisateur() chercherLesPrêtsEnCours() insérerPrêtEnCours () che rche rExemplai reParId Exemplaire() Table MembreGénéral Table Utilisateur Table Emp loyé 21/04/2017 Prêt datePrêt : Date lExemplaire : Exemplaire lUtilisateur : Utilisateur Table PrêtsEnCours Table Exemplaire Table Membre © Robert Godin. Tous droits réservés. 9 Classe Java pour Personne package ExemplesJDBC.SyLeRat; public class Personne { protected String nom; protected String prénom; public Personne(String nom,String prénom) { this.nom = nom; this.prénom = prénom; } public String getNom(){return nom;} public String getPrénom(){return prénom;} public void setNom(String nom){this.nom = nom;} public void setPrénom(String prénom){this.prénom = prénom;} } Lecteurs/modifieurs pour attributs (peut en faire un Java Bean) 21/04/2017 © Robert Godin. Tous droits réservés. 10 Classe Java pour Utilisateur package ExemplesJDBC.SyLeRat; import java.util.*; public class Utilisateur extends Personne { protected String idUtilisateur; protected String motPasse; protected String catégorie; protected Vector lesPrêts; // Association avec Prêt // NB Ce constructeur de crée pas lesPrêts public Utilisateur(String idUtilisateur, String motPasse, String nom, String prénom, String catégorie){ super(nom,prénom); this.idUtilisateur = idUtilisateur; this.motPasse = motPasse; this.catégorie = catégorie; } public String getIdUtilisateur(){return idUtilisateur;} public String getMotPasse(){return motPasse;} public String getCatégorie(){return catégorie;} public Vector getLesPrêts(){return lesPrêts;} public int getNbPrêtsEnCours(){ Enumeration enumerationPrêts = lesPrêts.elements(); int nbPrêtsEnCours = 0; while (enumerationPrêts.hasMoreElements()){ Prêt unPrêt = (Prêt)enumerationPrêts.nextElement(); if (unPrêt instanceof PrêtEnCours){ nbPrêtsEnCours++; } } return nbPrêtsEnCours; } public public public public } 21/04/2017 void void void void Lecteurs/modifieurs pour naviguer les associations Lecteur pour donnée dérivée setIdUtilisateur(String idUtilisateur){this.idUtilisateur = idUtilisateur;} setMotPasse(String motPasse){this.motPasse = motPasse;} setCatégorie(String catégorie){this.catégorie = catégorie;} setLesPrêts(Vector lesPrêts){this.lesPrêts = lesPrêts;} © Robert Godin. Tous droits réservés. 11 Classe Java pour Membre package ExemplesJDBC.SyLeRat; import java.util.*; public class Membre extends Utilisateur { private static int nbMaxPrêts; private static int duréeMaxPrêt; private String téléphoneRésidence; public Membre(String idUtilisateur, String motPasse, String nom, String prénom, String catégorie, String téléphoneRésidence){ super(idUtilisateur, motPasse, nom, prénom, catégorie); this.téléphoneRésidence = téléphoneRésidence; } public public public public 21/04/2017} static static static static Attributs et méthodes static pour attributs de classe (ou singleton) int getNbMaxPrêts(){return nbMaxPrêts;} int getDuréeMaxPrêt(){return duréeMaxPrêt;} void setNbMaxPrêts(int leNbMaxPrêts){nbMaxPrêts = leNbMaxPrêts;} void setDuréeMaxPrêts(int laDuréeMaxPrêt){duréeMaxPrêt = laDuréeMaxPrêt;} public void setTéléphoneRésidence(String téléphoneRésidence) {this.téléphoneRésidence = téléphoneRésidence;} public String getTéléphoneRésidence(){return téléphoneRésidence;} public int getNbRetards(){ Enumeration enumerationPrêts = lesPrêts.elements(); int nbRetards = 0; Calendar maintenant = Calendar.getInstance(); // Calendrier avec date actuelle // Soustrait ensuite la durée maximale en jours pour obtenir la date minimale // avant laquelle il y a un retard maintenant.add(Calendar.DATE,-duréeMaxPrêt); java.sql.Date dateMinSansRetard = new java.sql.Date(maintenant.getTime().getTime()); while (enumerationPrêts.hasMoreElements()){ Prêt unPrêt = (Prêt)enumerationPrêts.nextElement(); if (unPrêt instanceof PrêtEnCours){ if (dateMinSansRetard.after(unPrêt.getDatePrêt())){ nbRetards++; } } } return nbRetards; } public boolean conditionsPrêtAcceptées(){ return (this.getNbRetards() == 0 && this.getNbPrêtsEnCours() < nbMaxPrêts); } © Robert Godin. Tous droits réservés. 12 Classe Java pour Employé package ExemplesJDBC.GererPrets; import java.util.*; public class Employé extends Utilisateur { private String codeMatricule; private String catégorieEmployé; public Employé(String idUtilisateur, String motPasse, String nom, String prénom, String catégorie, String codeMatricule, String catégorieEmployé){ super(idUtilisateur, motPasse, nom, prénom, catégorie); this.codeMatricule = codeMatricule; this.catégorieEmployé = catégorieEmployé; } public String getCodeMatricule(){return codeMatricule;} public String getCatégorieEmployé(){return catégorieEmployé;} public void setCodeMatricule(String codeMatricule) {this.codeMatricule = codeMatricule;} public void setCatégorieEmployé(String catégorieEmployé) {this.catégorieEmployé = catégorieEmployé;} } 21/04/2017 © Robert Godin. Tous droits réservés. 13 Classe Java pour Prêt package ExemplesJDBC.SyLeRat; import java.sql.Date; public class Prêt { protected Utilisateur lUtilisateur; protected java.sql.Date datePrêt; protected Exemplaire lExemplaire; public Prêt(Utilisateur lUtilisateur,java.sql.Date datePrêt, Exemplaire lExemplaire) { this.lUtilisateur = lUtilisateur; this.datePrêt = datePrêt; this.lExemplaire = lExemplaire; } public java.sql.Date getDatePrêt(){return datePrêt;} public String getIdExemplaire(){return lExemplaire.getIdExemplaire();} public String getIdUtilisateur(){return lUtilisateur.getIdUtilisateur();} } 21/04/2017 © Robert Godin. Tous droits réservés. 14 Classe Java pour PrêtEnCours package ExemplesJDBC.GererPrets; public class PrêtEnCours extends Prêt { public PrêtEnCours(Utilisateur lUtilisateur,java.sql.Date datePrêt, lExemplaire) { super(lUtilisateur,datePrêt,lExemplaire); } // Le constructeur suivant ne construit pas le lien vers Exemplaire public PrêtEnCours(Utilisateur lUtilisateur,java.sql.Date datePrêt) { super(lUtilisateur,datePrêt,null); } } 21/04/2017 © Robert Godin. Tous droits réservés. Exemplaire 15 Classe Java pour Exemplaire package ExemplesJDBC.SyLeRat; public class Exemplaire { private String idExemplaire; private String statut; public Exemplaire(String idExemplaire,String statut) { this.idExemplaire = idExemplaire; this.statut = statut; } public String getStatut(){return statut;} public String getIdExemplaire(){return idExemplaire;} } 21/04/2017 © Robert Godin. Tous droits réservés. 16 CourtierBDUtilisateur package ExemplesJDBC.GererPrets; import java.sql.*; import java.util.*; public class CourtierBDUtilisateur{ private Connection uneConnection; // Constructeur pour connexion passée par le créateur public CourtierBDUtilisateur(Connection laConnection){ this.uneConnection = laConnection; } … public Utilisateur chercherUtilisateurParIdUtilisateur(String idUtilisateur) // Retourne un objet Membre ou un Employé selon le cas throws Exception { // Verrouillage de l'utilisateur pour sérialisabilité (Oracle mode READ COMMITTED) PreparedStatement unEnoncéSQL = uneConnection.prepareStatement ("SELECT motPasse,nom,prénom,catégorieUtilisateur,téléphoneRésidence,codeMatricule,catégorieEmployé "+ "FROM Membre m, Utilisateur u, Employé e WHERE u.idUtilisateur = ? AND "+ "u.idUtilisateur = m.idUtilisateur (+) AND "+ "u.idUtilisateur = e.idUtilisateur (+) FOR UPDATE"); unEnoncéSQL.setString(1,idUtilisateur); ResultSet résultatSelect = unEnoncéSQL.executeQuery(); if (résultatSelect.next ()){ if (résultatSelect.getString ("catégorieUtilisateur").equals("membre")){ Membre leMembre = new Membre( idUtilisateur,résultatSelect.getString ("motPasse"), résultatSelect.getString ("nom"),résultatSelect.getString ("prénom"), résultatSelect.getString ("catégorieUtilisateur"), résultatSelect.getString ("téléphoneRésidence")); unEnoncéSQL.close(); return leMembre; } else{// Pas un Membre, doit être un Employé Employé lEmployé = new Employé(idUtilisateur,résultatSelect.getString ("motPasse"); … 21/04/2017 © Robert Godin. Tous droits réservés. 17 Diagramme de séquence pour CourtierBDUtilisateur : ContrôleEnregistrerPrêts : CourtierBDUtilisateur :j ava.sql .Connection 1. «create»(uneConnection) :java.sql.Statement 2. chercherUtilisateur(idUtilisateur) 3. prepareState ment() 4. executeQuery() : Membre : Em pl oyé 5. [catégorie = 'membre']«create» 6. [catégorie='employé']«create» 7. unU til isateur 21/04/2017 © Robert Godin. Tous droits réservés. 18 Classe UsineConnection package ExemplesJDBC.GererPrets; import java.sql.*; public class UsineConnection { public UsineConnection() {} public Connection getConnectionSansAutoCommit( String nomPilote, // "oracle.jdbc.driver.OracleDriver" pour Oracle String URLBD, // exemple : "jdbc:oracle:thin:@localhost:1521:ora817i" String authorizationID, String password) throws Exception { Class.forName (nomPilote); Connection uneConnection = DriverManager.getConnection (URLBD, authorizationID,password); uneConnection.setAutoCommit(false); return uneConnection; } public Connection getConnection( String nomPilote, // "oracle.jdbc.driver.OracleDriver" pour Oracle String URLBD, // exemple : "jdbc:oracle:thin:@localhost:1521:ora817i" String authorizationID, String password) throws Exception { Class.forName (nomPilote); Connection uneConnection = DriverManager.getConnection (URLBD, authorizationID,password); return uneConnection; } } 21/04/2017 © Robert Godin. Tous droits réservés. 19 Courtier CourtierBDPrêtEnCours : chercherLesPrêtsEnCours package ExemplesJDBC.GererPrets; import java.sql.*; import java.util.*; public class CourtierBDPrêtEnCours { private Connection uneConnection; … public void chercherLesPrêtsEnCours(Utilisateur unUtilisateur) // Dématérialiser les PrêtEnCours d'un Utilisateur throws Exception{ // Verrouiller la table des prêts en cours pour sérialisabilité // (Oracle mode READ COMMITTED) PreparedStatement unEnoncéLock = uneConnection.prepareStatement( "LOCK TABLE PrêtEnCours IN SHARE ROW EXCLUSIVE MODE"); unEnoncéLock.execute(); unEnoncéLock.close(); PreparedStatement unEnoncéSQL = uneConnection.prepareStatement ("SELECT datePrêt,idExemplaire "+ "FROM PrêtEnCours WHERE idUtilisateur = ? " ); String idUtilisateur = unUtilisateur.getIdUtilisateur(); unEnoncéSQL.setString(1,idUtilisateur); ResultSet résultatSelect = unEnoncéSQL.executeQuery(); // lesPrêts contiendra la collection des objets PrêtEnCours Vector lesPrêts = new Vector(); while (résultatSelect.next ()){ PrêtEnCours unPrêt = new PrêtEnCours(unUtilisateur,résultatSelect.getDate(1)); lesPrêts.addElement(unPrêt); } // Faire les références inverses ! unUtilisateur.setLesPrêts(lesPrêts); unEnoncéSQL.close(); } … } 21/04/2017 © Robert Godin. Tous droits réservés. 20 Matérialisation d ’un PrêtEnCours : insérerPrêtEnCours package ExemplesJDBC.GererPrets; import java.sql.*; import java.util.*; public class CourtierBDPrêtEnCours { private Connection uneConnection; … public void insérerPrêtEnCours (PrêtEnCours unPrêtEnCours) // Matérialise un nouveau PrêtEnCours dans la BD throws Exception, SQLException { PreparedStatement unEnoncéSQL = uneConnection.prepareStatement ("INSERT INTO PrêtEnCours(idExemplaire,idUtilisateur)"+ "VALUES(?,?)"); unEnoncéSQL.setString(1,unPrêtEnCours.getIdExemplaire()); unEnoncéSQL.setString(2,unPrêtEnCours.getIdUtilisateur()); //NB la date est générée automatiquement par le serveur de BD (default sysdate) // et le statut est modifié par un TRIGGER int n = unEnoncéSQL.executeUpdate(); unEnoncéSQL.close(); if (n != 1){throw new Exception("Insertion dans PrêtEnCours a échoué");} } } 21/04/2017 © Robert Godin. Tous droits réservés. 21 Enjeux de la conception des courtiers Granularité des courtiers Granularité de matérialisation/dématérialisation – – 21/04/2017 minimiser les appels au serveur de BD limiter le volume de données à transférer © Robert Godin. Tous droits réservés. 22 Classe de contrôle ControleEnregistrerPrets package ExemplesJDBC.GererPrets; /* Classe de contrôle simple pour EnregistrerPrêts * Interface à l'utilisateur minimaliste * NB Vérifie les conditions de prêt et le statut de l'exemplaire au niveau du programme client */ import java.sql.*; import javax.swing.JOptionPane; import java.util.*; class ControleEnregistrerPrets { public static void main (String args []) throws Exception { // Création d'une Connection globale pour l'application UsineConnection uneUsineConnection = new UsineConnection(); Connection uneConnection = uneUsineConnection.getConnectionSansAutoCommit( "oracle.jdbc.driver.OracleDriver", "jdbc:oracle:thin:@localhost:1521:ora817i", "clerat","oracle"); try{ // Dématérialiser l'Utilisateur et ses PrêtsEnCours Membre unMembre = null; String idUtilisateur = JOptionPane.showInputDialog("Entrez l'identificateur de l'utilisateur: "); // Création du courtier et dématérialisation de l'Utilisateur CourtierBDUtilisateur unCourtierBDUtilisateur = new CourtierBDUtilisateur(uneConnection); Utilisateur unUtilisateur = unCourtierBDUtilisateur.chercherUtilisateurParIdUtilisateur(idUtilisateur); //Création du courtier et dématérialisation des PrêtsEnCours CourtierBDPrêtEnCours unCourtierBDPrêtEnCours = new CourtierBDPrêtEnCours(uneConnection); unCourtierBDPrêtEnCours.chercherLesPrêtsEnCours(unUtilisateur); JOptionPane.showMessageDialog(null, "Nombre de prêts en cours :" + unUtilisateur.getNbPrêtsEnCours()); … 21/04/2017 © Robert Godin. Tous droits réservés. 23 : Commis au prêt : ContrôleEnregistrerPrêts : CourtierBDPrêtsEnCours : CourtierBDUtilisateur : Usin eConnection : CourtierBDExemplaire 1. créerNouveauPrêt() uneConnection 2. getConnexionSansAutoCommit( ) 3. «create» 4. uneConnection 5 . entrer(i dUti lisateur) 6. «cre ate»(uneConnection) 7. chercherUtilisateur(idUtilisateur) uneTransaction 8. prepareStatement() 9. «create»(uneConnection) 10 . chercherLesPrêtsEnCours(un Uti lisateur ) 11. prepareStatement() 12. chercherVariablesStatiquesDeMembre( ) 13. prepareStatement() 14. entrer(i dExempl aire) 15. «create»(uneConnection) 16. chercherExem plaire (i dExempla ire ) 17. prepareStatement() 18. insérerPrêtEn Cou rs (unPrêtEnCours) 19. prepapreStatement() 20. commit() 21. cl ose() 21/04/2017 © Robert Godin. Tous droits réservés. 24 15.1.1 Enjeux de conception d'une application Java/JDBC : Commis au prêt : ContrôleEnregistrerPrêts : CourtierBDPrêtsEnCours : CourtierBDUtilisateur : Usin eConnection : CourtierBDExemplaire 1. créerNouveauPrêt() uneConnection 2. getConnexionSansAutoCommit( ) 3. «create» 4. uneConnection 5 . entrer(i dUti lisateur) 6. «cre ate»(uneConnection) Dépendance au temps de réflexion 7. chercherUtilisateur(idUtilisateur) uneTransaction 8. prepareStatement() 9. «create»(uneConnection) 10 . chercherLesPrêtsEnCours(un Uti lisateur ) 11. prepareStatement() 12. chercherVariablesStatiquesDeMembre( ) 13. prepareStatement() 14. entrer(i dExempl aire) 15. «create»(uneConnection) 16. chercherExem plaire (i dExempla ire ) 17. prepareStatement() 18. insérerPrêtEn Cou rs (unPrêtEnCours) 19. prepapreStatement() 21/04/2017 © Robert Godin. Tous droits réservés. 20. commit() 21. cl ose() 25 Limiter les verrouillages ? Transaction T1 Transaction T2 SQL> SELECT * FROM PrêtEnCours 2 WHERE idUtilisateur = 2; IDEXEMPLAI DATEPRÊT IDUTILISAT ---------- -------- ---------3 02-03-21 2 SQL> SELECT * FROM PrêtEnCours 2 WHERE idUtilisateur = 2; IDEXEMPLAI DATEPRÊT IDUTILISAT ---------- -------- ---------3 02-03-21 2 SQL> INSERT INTO 2 PrêtEnCours(idExemplaire,idUtilisateur) 3 VALUES(4,2); 1 row created. SQL> COMMIT; Commit complete. SQL> INSERT INTO 2 PrêtEnCours(idExemplaire,idUtilisateur) 3 VALUES(5,2); 1 row created. SQL> COMMIT; Commit complete. Dépasse la limite du nombre de prêts (maximum 2) 21/04/2017 © Robert Godin. Tous droits réservés. 26 Limiter la durée des transactions Contraintes de sérialisabilité ? 21/04/2017 © Robert Godin. Tous droits réservés. 27 Vérification des contraintes par le serveur Transaction T1 Transaction T2 SQL> SELECT * FROM PrêtEnCours 2 WHERE idUtilisateur = 2; IDEXEMPLAI DATEPRÊT IDUTILISAT ---------- -------- ---------3 02-03-21 2 SQL> SELECT * FROM PrêtEnCours 2 WHERE idUtilisateur = 2; IDEXEMPLAI DATEPRÊT IDUTILISAT ---------- -------- ---------3 02-03-21 2 Non sérialisabilité perçue par T2 SQL> INSERT INTO 2 PrêtEnCours(idExemplaire,idUtilisateur) 3 VALUES(4,2); 1 row created. SQL> COMMIT; Commit complete. SQL> INSERT INTO 2 PrêtEnCours(idExemplaire,idUtilisateur) 3 VALUES(5,2); INSERT INTO PrêtEnCours(idExemplaire,idUtilisateur) * ERROR at line 1: ORA-20101: le nombre maximal d'emprunts est atteint ORA-06512: at "CLERAT.BIPRÊTENCOURSVÉRIFIER", line 59 ORA-04088: error during execution of trigger 'CLERAT.BIPRÊTENCOURSVÉRIFIER' 21/04/2017 TRIGGER © RobertUn Godin. Tousempêche droits l'insertion réservés.fautive. 28 Contrôle de concurrence optimiste par estampillage explicite Lecture en une première transaction – Note l’estampille courante (ou numéro de version, …) Écriture en une deuxième transaction distincte – Vérifie si l’état a changé depuis la lecture Si non, met à jour les données et l’estampille Si oui, annulation Ne bloque pas les objets lus Populaire dans les applications Web 21/04/2017 © Robert Godin. Tous droits réservés. 29 Réduire l'interactivité Nom: EnregistrerPrêtsSimple Description courte : Enregistrer les prêts en un aller-retour au système Type: Interactif Description : Ce cas d'utilisation est déclenché par le commis au prêt suite à une requête d'un membre ou d'un employé et lui permet d'enregistrer un prêt en un aller-retour au système. Exigence d'intégrité : la séquence constitue une transaction Séquence normale d'interaction : Commis au prêt Système 1. Le commis invoque l'écran d'enregistrement du prêt 2. Le système demande la saisie de l'identificateur de l'utilisateur et de l’exemplaire 3. Le commis saisit l'identificateur de l'utilisateur à l'aide de la carte de membre ou manuellement et entre l'identificateur de l'exemplaire à l'aide du lecteur optique ou manuellement. 4. Le système enregistre le prêt, affiche un message d'acquiescement. 21/04/2017 © Robert Godin. Tous droits réservés. 30 ControleEnregistrerPretsSimple : Commis au prêt : ContrôleEnregistrerPrêts : UsineConnection : CourtierBDPrêtsEnCours 1. créerNouveauPrêt() uneConnection 2. getConnexion( ) 4. uneConnection 3. «create» 5. entrer(i dU til isate ur, i dExempl aire) 6. «create»(uneConnection) 7. insérerPrêtEnCours (unPrêtEnCours) 8. prepapreStatem ent() uneTransaction 9. close() 21/04/2017 © Robert Godin. Tous droits réservés. 31 Libérer la connexion entre les appels public typeRetour nomMéthodeCourtier(listeParamètres) throws Exception { Connection uneConnection = uneUsineConnection.getConnection… // Utiliser uneConnection … uneConnection.close(); } Transaction ne peut chevaucher les appels Ouverture/fermeture couteuse – 21/04/2017 Solution : connection pooling (JDBC 2) © Robert Godin. Tous droits réservés. 32 15.1.2 Gestion de la sécurité Qui gère l ’idUtilisateur et le mot de passe ? – – 21/04/2017 par les données : le SGBD par les traitements : le programme client © Robert Godin. Tous droits réservés. 33 ControleEnrPretsSecuriteParDonnees package ExemplesJDBC.GererPrets; /* Classe de contrôle simple pour EnregistrerPrêts * Interface à l'utilisateur minimaliste * NB Vérifie les conditions de prêt et le statut de l'exemplaire au niveau du programme client */ import java.sql.*; import javax.swing.JOptionPane; import java.util.*; class ControleEnrPretsSecuriteParDonnees { public static void main (String args []) throws Exception { String idCommis = JOptionPane.showInputDialog("Entrez l'identificateur du commis au prêt: "); String motDePasse = JOptionPane.showInputDialog("Entrez le mot de passe : "); // Création d'une Connection à partir du idCommis et de son motDePasse UsineConnection uneUsineConnection = new UsineConnection(); Connection uneConnection = uneUsineConnection.getConnectionSansAutoCommit( "oracle.jdbc.driver.OracleDriver", "jdbc:oracle:thin:@localhost:1521:ora817i", idCommis, motDePasse); try{ // Dématérialiser l'Utilisateur et ses PrêtsEnCours … // Le reste du code est identique à ControleEnregistrerPrets 21/04/2017 © Robert Godin. Tous droits réservés. 34 ControleEnrPretsSecuriteParTraitement package ExemplesJDBC.GererPrets; /* Classe de contrôle pour EnregistrerPrêts * Sécurité «par les traitements» * Interface à l'utilisateur minimaliste * NB Vérifie les conditions de prêt et le statut de l'exemplaire au niveau du programme client */ import java.sql.*; import javax.swing.JOptionPane; import java.util.*; class ControleEnrPretsSecuriteParTraitement { public static void main (String args []) throws Exception { 21/04/2017 // Création d'une Connection avec un authorizationID SQL indépendant du idCommis UsineConnection uneUsineConnection = new UsineConnection(); Connection uneConnection = uneUsineConnection.getConnectionSansAutoCommit( "oracle.jdbc.driver.OracleDriver", "jdbc:oracle:thin:@localhost:1521:ora817i", "clerat", "oracle"); try{ // Authentifier le commis au prêt (en utilisant la table Utilisateur) String idCommis = JOptionPane.showInputDialog("Entrez l'identificateur du commis au prêt: "); CourtierBDUtilisateur unCourtierBDUtilisateur = new CourtierBDUtilisateur(uneConnection); Utilisateur lUtilisateur = unCourtierBDUtilisateur.chercherUtilisateurParIdUtilisateur(idCommis); if (!(lUtilisateur instanceof Employé)){ JOptionPane.showMessageDialog(null,"Pas un employé :"+idCommis); }else { Employé leCommis = (Employé)lUtilisateur; if (!(leCommis.getCatégorieEmployé().equals("commis"))){ JOptionPane.showMessageDialog(null,"Pas un commis au prêt:"+idCommis); } else { String motDePasse = JOptionPane.showInputDialog("Entrez le mot de passe : "); if (!(leCommis.getMotPasse().equals(motDePasse))){ JOptionPane.showMessageDialog(null,"Mot de passe invalide:"+motDePasse); }else{//OK Commis est authentifié // Dématérialiser l'Utilisateur et ses PrêtsEnCours … // Le reste code est identique ControleEnregistrerPrets © du Robert Godin. Tous àdroits réservés. 35 15.1.3 Façade pour les services de la couche application ControleEnregistrerPretsAvecFacade FacadeEnregistrerPrets Personne Utili sateur Membre Connection 21/04/2017 UsineConnection CourtierBDUtilisateur Prêt PrêtEnCours CourtierBDPrêtsEnCours © Robert Godin. Tous droits réservés. Exemplaire CourtierBDExemplaire 36 Diagramme de séquence pour ControleEnregistrerPretAvecFacade : ControleEnregistrerPretsAvecFacade : FacadeEnregistrerPrets : UsineConnection uneConnection 1. «create» 1.1. getConnexionSan sAuto Commit( ) 1.1.1. «create» 2. u neC onnectio n 3. chercherOTDUtilisateurPrets() 4. getStatutExemplaire() 5. insererPretEnC ours( ) 6. confirmerTransaction( ) 7. commit() Dépendance réduite par rapport à la couche persistence 21/04/2017 8. close() © Robert Godin. Tous droits réservés. 37 Patron d'objet de transfert de données (OTD) uneClasseControle unOTD uneClasseFacade 1. getOTD() 2. unOTD 3. getPropriété1() 4. getPropriété2() 5. getPropriété3() etc. 21/04/2017 © Robert Godin. Tous droits réservés. 38 ControleEnregistrerPretsSimpleAvecFacade :ControleEnregistrerPretsSimpleAvecFacade :FacadeEnregistrerPretsSimple : Usi neC onne cti on uneConnection 1. «create» 2. insererPretEnCours(idUtilisateur,idExemplaire) 3. getConn ection() 4. «create» 5. une Con nection 6. cl ose() Connexion libérée entre les appels 21/04/2017 © Robert Godin. Tous droits réservés. 39 15.2 Développement d'applications Web par servlet Java Fureteur Web : - présentation Client mince 21/04/2017 Serveur d'application : -contrôle -domaine d'application SGBD - persistence - partie du domaine d'application ? Serveur d'application © Robert Godin. Tous droits réservés. Serveur BD 40 15.2.1 Principe de base du Web : le protocole HTTP et le langage de présentation HTML Requête HTTP Fureteur Web Serveur Web Réponse HTTP 21/04/2017 © Robert Godin. Tous droits réservés. 41 15.2.2 Développement de servlet Java Fureteur Web FORM HTML Serveur d'application (Web) 1. Le fureteur Invoque l'écran de saisie (HTTP GET) 2. Le serveur télécharge la FORM HTML 3. Le fureteur envoie des paramètres (HTTP POST) 4. Le serveur délègue l'appel au conteneur à servlet Conteneur à servlet 5. Le conteneur à servlet démarre un fil de la servlet servlet Java Serveur BD 6. La servelt fait appel au serveur de BD BD 7. La servelt produit la réponse HTTP qui est retournée au fureteur 21/04/2017 © Robert Godin. Tous droits réservés. 42 Diagramme de séquence de l'invocation de la servlet ServletEnregistrerPretSimpleFacade unFureteur unServeurWeb 1. requête HTTP POST : Facade En registrerPretSimple : ServletEnregistrerPretSimpleFacade 2. doPost(HttpServletRequest, H ttpServl etRe sponse) 3. insererPretEnCours(idUtilisateur,idExemplaire ) 4. date du prêt 5. HttpServle tRespon se 6. réponse HTTP (document HTML) Réutilisation des classes du client-serveur 21/04/2017 © Robert Godin. Tous droits réservés. 43 <html> <head> <title> Bibliothèque LeRat : Enregistrer un prêt (avec un servlet, interaction simplifiée) </title> </head> <body> <form action = "servlet/ServletEnregistrerPretsSimpleFacade" method = "post"> <p> Entrez l'identificateur de l'utilisateur et de l'exemplaire et cliquez Soumettre</p> <table> <tr> <td>Identificateur d'utilisateur</td> <td><input type = "text" name = "idUtilisateur" /></td> </tr> <tr> <td>Identificateur d'exemplaire</td> <td><input type = "text" name = "idExemplaire" /></td> </tr> </table> <input type = "submit" value = "Soumettre" /> </form> </body> </html> 21/04/2017 © Robert Godin. Tous droits réservés. 44 import import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*; java.sql.*; public class ServletEnregistrerPretsSimpleFacade extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) ServletException, IOException{ //Chercher le paramètre idUtilisateur de la FORM HTML et créer un Utilisateur String idUtilisateur = ""; try { idUtilisateur = request.getParameter("idUtilisateur"); } catch (Exception e) { e.printStackTrace(); } throws //Chercher le paramètre idExemplaire de la FORM HTML et créer un Exemplaire String idExemplaire = ""; try { idExemplaire = request.getParameter("idExemplaire"); } catch (Exception e) { e.printStackTrace(); } // Entête de la page de réponse HTML response.setContentType("text/html"); OutputStreamWriter unOutputStreamWriter = new OutputStreamWriter(response.getOutputStream()); PrintWriter out = new PrintWriter(unOutputStreamWriter); out.println("<html>"); out.println("<head><title>Réponse de ServletEnregistrerPretsSimpleFacadeEJB</title></head>"); out.println("<body>"); FacadeEnregistrerPretsSimple uneFacadeEnregistrerPretsSimple = null; try{ uneFacadeEnregistrerPretsSimple = new FacadeEnregistrerPretsSimple(); // Enregistrer le prêt en passant par la façade java.sql.Date datePret = uneFacadeEnregistrerPretsSimple.insererPretEnCours(idUtilisateur,idExemplaire); // Message de confirmation du prêt out.println("Prêt de l'exemplaire " + idExemplaire + " à l'utilisateur " + idUtilisateur + " confirmé.<br>Date :" + datePret); } … 21/04/2017 © Robert Godin. Tous droits réservés. 45 15.2.3 Cycle de vie d'une servlet Compilation et démarrage à la première invocation (par conteneur) – init() appelé une fois permet – – nouveau fil à chaque invocation d ’une méthode de service (doGet, doPost,…) destroy() à la fin e.g. 21/04/2017 d ’initialiser des ressources (e.g. connexion) fermer connexion © Robert Godin. Tous droits réservés. 46 15.2.4 Gestion d'une session HTTP dans une servlet La FORM n ’envoie que le idUtilisateur 21/04/2017 © Robert Godin. Tous droits réservés. 47 FormIdentificationUtilisateurFacade.html <html> <head> <title> Exemple de session http pour cas Enregistrer Prêts : Identification utilisateur </title> </head> <body> <form action = "servlet/ServletIdentificationUtilisateurFacade" method = "post"> <p>Entrez l'identifiant de l'utilisateur et cliquez Soumettre</p> <input type = "text" name = "idUtilisateur" /> <input type = "submit" value = "Soumettre" /> </form> </body> </html> 21/04/2017 © Robert Godin. Tous droits réservés. 48 EnregistrerPrets en deux servlets unFureteur unServeurWeb : ServletIdentificationUtilisateurFacade 1. requête HTTP POST pour ServletIdentificationUtilisateurFacade uneSession : HttpSession 2. doPost(HttpServletRequest, HttpServletResponse) 3. «create» 4. « crea te» uneFacadeEnregistrerPrets : FacadeEnregistrerPrets 5. chercherOTDUtilisateurPrets() uneTransaction 6. setAttribute('uneFacadeEnregistrerPrets', uneFacadeEnregistrerPrets) 7. setAttribute('i dU til is ateu r',i dUtil is ateur) 8. HttpServletResponse : ServletTerminerPretFacade 9. réponse HTTP (FormTerminerPret) 10. requête HTTP POST pour ServletTerminerPretFacade 11. doPost(HttpServletRequest, HttpServletResponse) 1 2. getAttribute('un eFacadeEn regi strerPrets') 1 3. getAttribute('idU til isa teur') 1 4. getStatutExem plaire() 15. insererPretEnCo urs(id Utilisateur,idExempla ire ) 16 . confirm erTransacti on( ) 17. HttpServletResponse 18. réponse HTTP (confirmation) 21/04/2017 © Robert Godin. Tous droits réservés. 49 … public class ServletIdentificationUtilisateurFacade extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //Chercher le paramètre idUtilisateur de la form String idUtilisateur = ""; try { idUtilisateur = request.getParameter("idUtilisateur"); } catch (Exception e) { e.printStackTrace(); } // Créer un objet session si non existant HttpSession uneSession = request.getSession(true); // Entête de la page de réponse html response.setContentType("text/html"); … try{ // Créer la facade et chercher l'OTDUtilisateurPrets FacadeEnregistrerPrets uneFacadeEnregistrerPrets = new FacadeEnregistrerPrets(); OTDUtilisateurPrets unOTDUtilisateurPrets = uneFacadeEnregistrerPrets.chercherOTDUtilisateurPrets(idUtilisateur); // Affichage de l'idUtilisateur et du nombre de prêts out.println("Utilisateur :" + idUtilisateur); out.println(" Nombre de prêts en cours :" + unOTDUtilisateurPrets.getNbPretsEnCours()+"<br>"); // Vérifier si les conditions préalables sont violées … }else{ // Sauvegarder les objets du contexte de la session uneSession.setAttribute("uneFacadeEnregistrerPrets", uneFacadeEnregistrerPrets); uneSession.setAttribute("idUtilisateur", idUtilisateur); // Construire la FORM pour saisir l'idExemplaire out.println("<form action = \"ServletTerminerPretFacade\" method = \"post\">"); out.println("<p>Entrez l'identifiant de l'exemplaire et cliquez Soumettre</p>"); out.println("<input type = \"text\" name = \"idExemplaire\" />"); out.println("<input type = \"submit\" value = \"Soumettre\" />"); out.println("</form>"); } ... 21/04/2017 © Robert Godin. Tous droits réservés. 50 … public class ServletTerminerPretFacade extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //Chercher le paramètre idUtilisateur de la form String idExemplaire = ""; try { idExemplaire = request.getParameter("idExemplaire"); } catch (Exception e) { e.printStackTrace(); } // Chercher l'objet de la session en cours HttpSession uneSession = request.getSession(false); // Chercher les objets du contexte de la session en cours FacadeEnregistrerPrets uneFacadeEnregistrerPrets = (FacadeEnregistrerPrets)uneSession.getAttribute("uneFacadeEnregistrerPrets"); String idUtilisateur = (String)uneSession.getAttribute("idUtilisateur"); // Entête de la page de réponse html … try{ // Vérifier statut de l'exemplaire String statut = uneFacadeEnregistrerPrets.getStatutExemplaire(idExemplaire); if (statut.equals("disponible")){ // Enregistrer le prêt en passant par la façade java.sql.Date datePret = uneFacadeEnregistrerPrets.insererPretEnCours(); … finally{ try{ out.println("</body></html>"); out.close(); uneFacadeEnregistrerPrets.confirmerTransaction(); } catch(Exception lException){ lException.printStackTrace(); } } } } 21/04/2017 © Robert Godin. Tous droits réservés. 51 15.2.5 DataSource, ConnectionPoolDataSource et PooledConnection Passage par serveur JNDI (Java Naming and Directory Interface) – – – 21/04/2017 e.g. service d’annuaire LDAP repérage de ressources (e.g. serveur BD) accès sécurisé © Robert Godin. Tous droits réservés. 52 Ouverture d ’une Connection en passant par DataSource package ExemplesJDBC.GererPrets; import java.sql.*; import javax.naming.*; import javax.sql.*; public class UsineConnection { public UsineConnection() {} … public Connection getConnectionFromDataSource( String nomDataSource) throws Exception { InitialContext unContexte = new InitialContext(); DataSource unDataSource = (DataSource)unContexte.lookup(nomDataSource); Connection uneConnection = unDataSource.getConnection(); return uneConnection; } } 21/04/2017 © Robert Godin. Tous droits réservés. 53 Enregistrement du DataSource par descripteur de déploiement XML pour serveur embarqué OC4J de Oracle9i JDevelopper (généré par JDevelopper) <?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE data-sources PUBLIC "Orion data-sources" "http://xmlns.oracle.com/ias/dtds/data-sources.dtd"> <data-sources> <data-source class="com.evermind.sql.DriverManagerDataSource" connection-driver="oracle.jdbc.driver.OracleDriver" ejb-location="jdbc/ora9icleratDS" inactivity-timeout="30" location="jdbc/ora9icleratCoreDS" name="ora9icleratDS" password="oracle" pooled-location="jdbc/ora9icleratPooledDS" url="jdbc:oracle:thin:@localhost:1521:ora9i" username="clerat" xa-location="jdbc/xa/ora9icleratXADS"/> </data-sources> Connection uneConnection = uneUsineConnection.getConnectionFromDataSource("jdbc/ora9icleratCoreDS"); 21/04/2017 © Robert Godin. Tous droits réservés. 54 Partage d'un pool de connexions package ExemplesJDBC.GererPrets; import java.sql.*; import javax.naming.*; import javax.sql.*; import oracle.jdbc.pool.*; public class UsineConnection { public UsineConnection() {} … public Connection getConnectionFromConnectionPoolDataSource( String nomDataSource) throws Exception { InitialContext unContexte = new InitialContext(); ConnectionPoolDataSource unDataSource = (ConnectionPoolDataSource)unContexte.lookup(nomDataSource); PooledConnection unePooledConnection = unDataSource.getPooledConnection(); Connection uneConnection = unePooledConnection.getConnection(); return uneConnection; } } 21/04/2017 © Robert Godin. Tous droits réservés. 55 15.2.6 Mécanismes de sécurité pour l'accès à un servlet Sécurité programmatique Sécurité déclarative par le conteneur – – 21/04/2017 rôles/utilisateurs géré par conteneur © Robert Godin. Tous droits réservés. 56 15.3 Architecture pour la persistance transparente Persistance gérée par la classe métier Transparent au client Mécanisme paresseux de matérialisation/dématérialisation – – Norme Java Data Objects (JDO) – – – gestion de cache transactionnelle programmation complexe http://access1.sun.com/jdo/ inspirée de ODMG outils : http://www.javaskyline.com/database.html Nouvelle norme Java Persistence API (JPA) – 21/04/2017 Inspirée des meilleures solutions (JDO, Hibernate, Toplink, …) © Robert Godin. Tous droits réservés. 57 15.4 Norme EJB de J2EE (http://java.sun.com/products/ejb/) Transparence « extrême » – – – persistance répartition services Spécification déclarative de services – – – comportement transactionnel sécurité gestion de ressources (CPU, mémoire, serveurs, ...) e.g. partage transparent d’un bassin d’objets EJB (pooling) entre clients robustesse, « scalabilité » Prise en charge des services par le conteneur à EJB 21/04/2017 © Robert Godin. Tous droits réservés. 58 Séparation propre des responsabilités (rôles) Fournisseur de EJB (EnterpriseBean Provider) – Assembleur d'application (Application Assembler) . – serveur EJB : services de bas niveau pour le conteneur EJB Fournisseur de conteneur EJB (EJB Container Provider) – déploie les EJB dans leur environnement d'exécution Fournisseur de serveur EJB (EJB Server Provider). – assemble EJB + servlet, JSP, HTML, etc. Déployeur (Deployer) – développe les composantes EJB conteneur EJB : environnement de déploiement et d'exécution des EJB Administrateur de Système (System Administrator) – 21/04/2017 administre serveur et le conteneur EJB © Robert Godin. Tous droits réservés. 59 Catégories de EJB EJB session (Session Bean) – – – – service d ’objet distant un seul client à la fois par objet partage d’un bassin d’objets entre clients état au besoin entre les appels – non persistent (perdu suite à une panne) EJB entité (Entity Bean) – – – stateful/stateless session EJB services d'objet distant persistant objet ~ proxy pour une ligne d'une table un objet EJB entité peut être partagé par plusieurs clients EJB message (Message Bean) – 21/04/2017 service de message © Robert Godin. Tous droits réservés. 60 Codage d ’un EJB Interface – usine à objet EJB Interface – 21/04/2017 à distance (remote) accès aux méthodes d'un objet EJB Classe – home bean (bean class) implémente les méthodes de l'objet EJB © Robert Godin. Tous droits réservés. 61 15.4.1 Développement d'un EJB session sans état : FacadeEnrPretSimpleSessionEJB Interface home (usine à EJB) package ExemplesJDBC.GererPrets; import javax.ejb.EJBHome; import java.rmi.RemoteException; import javax.ejb.CreateException; public interface FacadeEnrPretSimpleSessionEJBHome extends EJBHome { FacadeEnrPretSimpleSessionEJB create() throws RemoteException, CreateException; } Interface remote (interface à l’objet EJB) package ExemplesJDBC.GererPrets; import javax.ejb.EJBObject; import java.rmi.RemoteException; import java.sql.Date; public interface FacadeEnrPretSimpleSessionEJB extends EJBObject { Date insererPretEnCours(String idUtilisateur, String idExemplaire) throws RemoteException, Exception; } NB Paramètres sérialisables (au sens de Java) passés par valeur 21/04/2017 © Robert Godin. Tous droits réservés. 62 package ExemplesJDBC.GererPrets.impl; import javax.ejb.SessionBean; import javax.ejb.SessionContext; import java.sql.Date; import ExemplesJDBC.GererPrets.*; import java.sql.Connection; import java.util.*; public class FacadeEnrPretSimpleSessionEJBBean implements SessionBean { public void ejbCreate(){} public void ejbActivate(){} public void ejbPassivate(){} public void ejbRemove(){} public void setSessionContext(SessionContext ctx){} public Date insererPretEnCours(String idUtilisateur, String idExemplaire) throws Exception { UsineConnection uneUsineConnection = new UsineConnection(); Connection uneConnection = uneUsineConnection.getConnectionFromDataSource("jdbc/ora9icleratCoreDS"); Utilisateur unUtilisateur = new Utilisateur(idUtilisateur); Exemplaire unExemplaire = new Exemplaire(idExemplaire); // Générer la date du jour et l'objet PrêtEnCours Calendar maintenant = Calendar.getInstance(); // Calendrier avec date actuelle java.sql.Date dateMaintenant = new java.sql.Date(maintenant.getTime().getTime()); PrêtEnCours leNouveauPrêtEnCours = new PrêtEnCours(unUtilisateur,dateMaintenant,unExemplaire); // Matérialiser le PrêtEnCours dans la BD CourtierBDPrêtEnCours unCourtierBDPrêtEnCours = new CourtierBDPrêtEnCours(uneConnection); unCourtierBDPrêtEnCours.insérerPrêtEnCours(leNouveauPrêtEnCours); // Pas besoin de commit uneConnection.close(); return dateMaintenant; } } 21/04/2017 © Robert Godin. Tous droits réservés. 63 Descripteur de déploiement XML <?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <enterprise-beans> <session> <description>Session Bean ( Stateless )</description> <display-name>FacadeEnrPretSimpleSessionEJB</display-name> <ejb-name>FacadeEnrPretSimpleSessionEJB</ejb-name> <home>ExemplesJDBC.GererPrets.FacadeEnrPretSimpleSessionEJBHome</home> <remote>ExemplesJDBC.GererPrets.FacadeEnrPretSimpleSessionEJB</remote> <ejb-class>ExemplesJDBC.GererPrets.impl.FacadeEnrPretSimpleSessionEJBBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Bean</transaction-type> </session> </ejb-jar> 21/04/2017 © Robert Godin. Tous droits réservés. 64 Client du EJB : façade locale qui délègue à la façade EJB distante (patron Business Delegate) package ExemplesJDBC.GererPrets; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import ExemplesJDBC.GererPrets.FacadeEnrPretSimpleSessionEJB; import ExemplesJDBC.GererPrets.FacadeEnrPretSimpleSessionEJBHome; // Façade pour le cas d'utilisation EnregistrerPretsSimple // La façade utilise les services du EJB session FacadeEnrPretSimpleSessionEJB public class FacadeEJBEnregistrerPretsSimple { public FacadeEJBEnregistrerPretsSimple()throws Exception { } // Insère le prêt et retourne la date d'enregistrement public java.sql.Date insererPretEnCours( String idUtilisateur, String idExemplaire) throws Exception{ // Chercher une référence au EJB session FacadeEnrPretSimpleSessionEJB uneFacadeEnrPretSimpleSessionEJB; Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.rmi.RMIInitialContextFactory"); env.put(Context.SECURITY_PRINCIPAL, "admin"); env.put(Context.SECURITY_CREDENTIALS, "welcome"); env.put(Context.PROVIDER_URL, "ormi://localhost:23893/current-workspace-app"); Context ctx = new InitialContext(env); FacadeEnrPretSimpleSessionEJBHome facadeEnrPretSimpleSessionEJBHome = (FacadeEnrPretSimpleSessionEJBHome)ctx.lookup("FacadeEnrPretSimpleSessionEJB"); uneFacadeEnrPretSimpleSessionEJB = facadeEnrPretSimpleSessionEJBHome.create(); // Déléguer l'insertion du PrêtEnCours à la méthode du EJB return uneFacadeEnrPretSimpleSessionEJB.insererPretEnCours(idUtilisateur,idExemplaire); } } 21/04/2017 © Robert Godin. Tous droits réservés. 65 JDevelopper 10g (déploiement de défaut différent) // Chercher une référence au EJB session Context context = new InitialContext(); FacadeEnrPretSimpleSessionEJBHome facadeEnrPretSimpleSessionEJBHome = (FacadeEnrPretSimpleSessionEJBHome)PortableRemoteObject.narrow( context.lookup("FacadeEnrPretSimpleSessionEJB"), FacadeEnrPretSimpleSessionEJBHome.class); FacadeEnrPretSimpleSessionEJB uneFacadeEnrPretSimpleSessionEJB; uneFacadeEnrPretSimpleSessionEJB = facadeEnrPretSimpleSessionEJBHome.create(); 21/04/2017 © Robert Godin. Tous droits réservés. 66 Interposition transparente d ’objets Conteneur de bean Client du EJB : Con trole Enregi strerPretsSi mpl eAvecFacadeEJB : FacadeEJBEnregistrerPretsSimple Ob jet home In terpositi on transparente d'o bjets générés par le conten eur pour e ffectuer l'envoie de message à l'obj et be an 1. insererPretEnCours(id Utilisateur,idExemplaire) ObjetEJB local qui implémente l'interface remote ctx : Context 2. «create» 3. lookup() facadeEnrPretSimpl eSessionEJBHome 4. facadeEnrPretSimpleSessionEJBHome 5. create() Objet bean uneFacad eEnrPretSim ple SessionEJB : FacadeEnregistrerPretsSimpleSessionEJB 6. uneFacadeEnrPretSimpleSessionEJB : FacadeEn regi strerPretsSimpl eSessi onEJBBean 7 . in sererPretEnCours(idUtil is ateu r,idExemp laire ) 8. insererPretEnCou rs(idUtilisateur,idExemplaire) Poste du client du EJB 21/04/2017 © Robert Godin. Tous droits réservés. Conteneur EJB 67 Dans ServletEnregistrerPretsSimpleFacade remplacer FacadeEnregistrerPretsSimple par une FacadeEJBEnregitrerPretSimple package ExemplesJDBC.GererPrets; import import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*; java.sql.*; public class ServletEnregistrerPretsSimpleFacadeEJB extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ … FacadeEJBEnregistrerPretsSimple uneFacadeEnregistrerPretsSimple = null; try{ uneFacadeEnregistrerPretsSimple = new FacadeEJBEnregistrerPretsSimple(); … } } 21/04/2017 © Robert Godin. Tous droits réservés. 68 15.4.2 Développement d'un EJB session avec état package ExemplesJDBC.GererPrets; import javax.ejb.EJBHome; import java.rmi.RemoteException; import javax.ejb.CreateException; public interface FacadeEnrPretsSessionEJBHome extends EJBHome { FacadeEnrPretsSessionEJB create() throws RemoteException, CreateException; } package ExemplesJDBC.GererPrets; import javax.ejb.EJBObject; import java.rmi.RemoteException; import java.sql.Date; public interface FacadeEnrPretsSessionEJB extends EJBObject { OTDUtilisateurPrets chercherOTDUtilisateurPrets(String idUtilisateur) throws Exception, RemoteException; void confirmerTransaction() throws Exception, RemoteException; String getStatutExemplaire(String idExemplaire) throws Exception, RemoteException; Date insererPretEnCours() throws Exception, RemoteException; } 21/04/2017 © Robert Godin. Tous droits réservés. 69 Classe bean package ExemplesJDBC.GererPrets.impl; import javax.ejb.SessionBean; import javax.ejb.SessionContext; import java.sql.Connection; import ExemplesJDBC.GererPrets.*; import java.sql.Date; import java.util.*; État maintenu entre les appels public class FacadeEnrPretsSessionEJBBean implements SessionBean { private Connection uneConnection; private Utilisateur unUtilisateur; private Exemplaire unExemplaire; private CourtierBDPrêtEnCours unCourtierBDPretEnCours; public void ejbCreate()throws Exception{} public void ejbActivate(){} public void ejbPassivate(){} public void ejbRemove(){} Méthode du cycle de vie appelée à la création de l’objet avant ejbCreate() public void setSessionContext(SessionContext ctx) { try { UsineConnection uneUsineConnection = new UsineConnection(); uneConnection = uneUsineConnection.getConnectionFromDataSourceSansAutoCommit("jdbc/ora9icleratCoreDS"); } catch(Exception lException){ lException.printStackTrace(); } } NB setSessionContext() permet d’initialiser la connexion au DataSource (interdit dans ejbCreate()…) … 21/04/2017 © Robert Godin. Tous droits réservés. 70 Classe Bean (suite) : méthode métier identique à FacadeEnregistrerPrets public OTDUtilisateurPrets chercherOTDUtilisateurPrets(String idUtilisateur) throws Exception { // Création du courtier et dématérialisation de l'Utilisateur CourtierBDUtilisateur unCourtierBDUtilisateur = new CourtierBDUtilisateur(uneConnection); unUtilisateur = unCourtierBDUtilisateur.chercherUtilisateurParIdUtilisateur(idUtilisateur); //Création du courtier et dématérialisation des PrêtsEnCours unCourtierBDPretEnCours = new CourtierBDPrêtEnCours(uneConnection); unCourtierBDPretEnCours.chercherLesPrêtsEnCours(unUtilisateur); // Retourner l'objet de transfert de données OTDUtilisateurPrets if (unUtilisateur instanceof Membre){ unCourtierBDUtilisateur.chercherVariablesStatiquesDeMembre(); Membre unMembre = (Membre)unUtilisateur; return new OTDUtilisateurPrets( unMembre.getCatégorie(), unMembre.conditionsPrêtAcceptées(), unMembre.getNbPrêtsEnCours(), Membre.getNbMaxPrêts(), unMembre.getNbRetards()); } else { return new OTDUtilisateurPrets( unUtilisateur.getCatégorie(),true,unUtilisateur.getNbPrêtsEnCours(),0,0); } } 21/04/2017 © Robert Godin. Tous droits réservés. 71 Processus de création d ’un EJB avec état unClientEJB unEJBHome unObjetEJBl oca l u nSessio nContext unObjetEJBBean 1. create() 2. «create» 3. «create» 4. «create» 5. setSessionContext() 6. ejbCreate() 21/04/2017 © Robert Godin. Tous droits réservés. 72 Client du EJB : façade locale qui délègue à la façade EJB distante … public class FacadeEJBEnregistrerPrets { private FacadeEnrPretsSessionEJB uneFacadeEnrPretsSessionEJB; public FacadeEJBEnregistrerPrets()throws Exception { // Le constructeur crée l'objet session EJB façade Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY,"com.evermind.server.rmi.RMIInitialContextFactory"); env.put(Context.SECURITY_PRINCIPAL, "admin"); env.put(Context.SECURITY_CREDENTIALS, "welcome"); env.put(Context.PROVIDER_URL, "ormi://localhost:23893/current-workspace-app"); Context ctx = new InitialContext(env); FacadeEnrPretsSessionEJBHome facadeEnrPretsSessionEJBHome = (FacadeEnrPretsSessionEJBHome)ctx.lookup("FacadeEnrPretsSessionEJB"); uneFacadeEnrPretsSessionEJB = facadeEnrPretsSessionEJBHome.create( ); } // Cherche un Utilisateur avec ses PretsEnCours et retourne // les données nécessaires pour EnregistrerPrets (OTDUtilisateurPrets) // Une exception est levée si l'Utilisateur n'existe pas public OTDUtilisateurPrets chercherOTDUtilisateurPrets(String idUtilisateur)throws Exception { return uneFacadeEnrPretsSessionEJB. chercherOTDUtilisateurPrets(idUtilisateur); } … 21/04/2017 © Robert Godin. Tous droits réservés. 73 Dans ServletIdentificationUtilisateurFacade et ServletTerminerPretFacade remplacer FacadeEnregistrerPrets par une FacadeEJBEnregistrerPrets package ExemplesJDBC.GererPrets; import import import import import javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*; java.sql.*; public class ServletIdentUtilFacadeEJB extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { … try{ // Créer la facade et chercher l'OTDUtilisateurPrets FacadeEJBEnregistrerPrets uneFacadeEnregistrerPrets = new FacadeEJBEnregistrerPrets(); OTDUtilisateurPrets unOTDUtilisateurPrets = uneFacadeEnregistrerPrets.chercherOTDUtilisateurPrets(idUtilisateur); … 21/04/2017 © Robert Godin. Tous droits réservés. 74 15.4.3 Support de transactions distribuées Plusieurs ressources indépendantes – serveur BD, serveur JMS, ... Protocole transparent de transaction répartie Démarcation programmatique ou déclarative 21/04/2017 © Robert Godin. Tous droits réservés. 75 15.4.4 Démarcation programmatique de transaction distribuée (bean managed transaction demarcation) CREATE TABLE Compte (noCompte INTEGER PRIMARY KEY, solde DECIMAL(10,2) CHECK (solde >= 0), dateOuverture DATE, noClient) package mypackage1; import javax.ejb.*; import java.rmi.RemoteException; import java.sql.Connection; public interface BanqueSessionBMTDEJB extends EJBObject { void transferer(int noCompteADebiter, int noCompteACrediter, double montant) throws RemoteException, EJBException; } Deux connexions indépendantes ! 21/04/2017 © Robert Godin. Tous droits réservés. 76 Classe bean … // Exemple de EJB session qui réalise une transaction répartie // Démarcation programmatique de transaction avec javax.transation.UserTransaction public class BanqueSessionBMTDEJBBean implements SessionBean { private SessionContext sessionContext; private Connection connectionBanqueDebit; private Connection connectionBanqueCredit; … public void setSessionContext(SessionContext ctx){ sessionContext = ctx; ouvrirConnections(); } Deux connexions void ouvrirConnections() throws EJBException { try{ UsineConnection uneUsineConnection = new UsineConnection(); connectionBanqueDebit = uneUsineConnection.getConnectionFromDataSource("jdbc/ora9igodinCoreDS"); connectionBanqueCredit = uneUsineConnection.getConnectionFromDataSource("jdbc/ora9igodin2CoreDS"); } catch(Exception lException){ throw new EJBException(lException); } } … 21/04/2017 © Robert Godin. Tous droits réservés. 77 Classe bean public void transferer(int noCompteADebiter, int noCompteACrediter, double montant) throws EJBException { try{ // Chercher le UserTransaction du sessionContext UserTransaction uneUserTransaction = sessionContext.getUserTransaction(); // Début de la transaction répartie uneUserTransaction.begin(); // Débiter le compte de connectionBanqueDebit PreparedStatement preparedStatementDebit = connectionBanqueDebit.prepareStatement ("UPDATE Compte SET solde = solde - ? WHERE noCompte = ?"); preparedStatementDebit.setDouble(1,montant); preparedStatementDebit.setInt(2,noCompteADebiter); int n = preparedStatementDebit.executeUpdate(); preparedStatementDebit.close(); if (n != 1){throw new EJBException("Retrait a échoué");} // Créditer le compte de connectionBanqueCredit PreparedStatement preparedStatementCredit = connectionBanqueCredit.prepareStatement ("UPDATE Compte SET solde = solde + ? WHERE noCompte = ?"); preparedStatementCredit.setDouble(1,montant); preparedStatementCredit.setInt(2,noCompteACrediter); n = preparedStatementCredit.executeUpdate(); if (n != 1){throw new EJBException("Dépôt a échoué");} preparedStatementCredit.close(); // Confirmer la transation répartie uneUserTransaction.commit(); 21/04/2017 © Robert Godin. Tous droits réservés. 78 Descripteur de déploiement XML <?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <enterprise-beans> <session> <description>Session Bean ( Stateful )</description> <display-name>BanqueSessionBMTDEJB</display-name> <ejb-name>BanqueSessionBMTDEJB</ejb-name> <home>mypackage1.BanqueSessionBMTDEJBHome</home> <remote>mypackage1.BanqueSessionBMTDEJB</remote> <ejb-class>mypackage1.impl.BanqueSessionBMTDEJBBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Bean</transaction-type> </session> </enterprise-beans> </ejb-jar> 21/04/2017 © Robert Godin. Tous droits réservés. JavaBeans 1.1//EN" 79 15.4.5 Démarcation déclarative de transaction (container managed transaction demarcation) <?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <enterprise-beans> <session> <description>Session Bean ( Stateless )</description> <display-name>BanqueSessionCMTDEJB</display-name> <ejb-name>BanqueSessionCMTDEJB</ejb-name> <home>mypackage1.BanqueSessionCMTDEJBHome</home> <remote>mypackage1.BanqueSessionCMTDEJB</remote> <ejb-class>mypackage1.impl.BanqueSessionCMTDEJBBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>BanqueSessionCMTDEJB</ejb-name> <method-name>tranferer</method-name> <method-params> <method-param>int</method-param> <method-param>int</method-param> <method-param>double</method-param> </method-params> </method> <trans-attribute>RequiresNew</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar> 21/04/2017 © Robert Godin. Tous droits réservés. JavaBeans 1.1//EN" 80 Classe bean // Attribut de transaction RequiresNew dans le descripteur de déploiement XML public void transferer(int noCompteADebiter, int noCompteACrediter, double montant) throws EJBException{ try{ // Débiter le compte de connectionBanqueDebit PreparedStatement preparedStatementDebit = connectionBanqueDebit.prepareStatement ("UPDATE Compte SET solde = solde - ? WHERE noCompte = ?"); preparedStatementDebit.setDouble(1,montant); preparedStatementDebit.setInt(2,noCompteADebiter); int n = preparedStatementDebit.executeUpdate(); preparedStatementDebit.close(); if (n != 1){throw new EJBException("Retrait a échoué");} // Créditer le compte de connectionBanqueCredit PreparedStatement preparedStatementCredit = connectionBanqueCredit.prepareStatement ("UPDATE Compte SET solde = solde + ? WHERE noCompte = ?"); preparedStatementCredit.setDouble(1,montant); preparedStatementCredit.setInt(2,noCompteACrediter); n = preparedStatementCredit.executeUpdate(); if (n != 1){throw new EJBException("Dépôt a échoué");} preparedStatementCredit.close(); } catch(Exception lException){ throw new EJBException(lException); } } 21/04/2017 © Robert Godin. Tous droits réservés. 81 balise <trans-attribut> NotSupported Required Supports RequiresNew Mandatory Never 21/04/2017 © Robert Godin. Tous droits réservés. 82 15.4.6 EJB entité Bean Managed Persistence - BMP – codage du SQL/JDBC Container Managed Persistence - CMP – – – – 21/04/2017 SQL généré par conteneur associations depuis EJB 2 héritage avec EJB3 clé primaire obligatoire ... © Robert Godin. Tous droits réservés. 83 Classe de clé primaire package mypackage1; import java.io.Serializable; public class CompteEJBPK implements Serializable { public int noCompte; // La classe PK doit conteneur) public CompteEJBPK(){} avoir un constructeur vide (utilisé par le public CompteEJBPK(int noCompte){ this.noCompte = noCompte; } public boolean equals(Object unObjet){ return super.equals(unObjet); } public int hashCode(){ return super.hashCode(); } } 21/04/2017 © Robert Godin. Tous droits réservés. 84 Interface remote package mypackage1; import javax.ejb.EJBObject; import java.rmi.RemoteException; import java.sql.Date; public interface CompteEJB extends EJBObject { int getNoCompte() throws RemoteException; double getSolde() throws RemoteException; void setSolde(double newSolde) throws RemoteException; Date getDateOuverture() throws RemoteException; void setDateOuverture(Date newDateouverture) throws RemoteException; int getNoClient() throws RemoteException; void setNoClient(int newNoclient) throws RemoteException; } 21/04/2017 © Robert Godin. Tous droits réservés. 85 Interface home package mypackage1; import javax.ejb.EJBHome; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; import java.util.Collection; public interface CompteEJBHome extends EJBHome { CompteEJB create(int noCompte, java.sql.Date RemoteException, CreateException; dateOuverture, int noClient) throws CompteEJB findByPrimaryKey(CompteEJBPK primaryKey) throws RemoteException, FinderException; Collection findAll() throws RemoteException, FinderException; Collection findByNoClient(int noClient) throws RemoteException, FinderException; } 21/04/2017 © Robert Godin. Tous droits réservés. 86 Classe bean package mypackage1.impl; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import mypackage1.CompteEJBPK; import java.sql.Date; public int getNoCompte(){ return noCompte; } public class CompteEJBBean implements EntityBean { public EntityContext entityContext; public int noCompte; public double solde; public Date dateOuverture; public int noClient; public double getSolde(){ return solde; } public void setSolde(double newSolde){ solde = newSolde; } public CompteEJBPK ejbCreate(int noCompte, Date dateOuverture, int noClient) { this.noCompte = noCompte; this.dateOuverture = dateOuverture; this.noClient = noClient; return null; } public Date getDateOuverture(){ return dateOuverture; } public void setDateOuverture(Date newDateouverture){ dateOuverture = newDateouverture; } public void ejbPostCreate(int noCompte, Date dateOuverture, int noClient){} public int getNoClient(){ return noClient; } public void ejbActivate(){} public void ejbLoad(){} public void setNoClient(int newNoclient){ noClient = newNoclient; } public void ejbPassivate(){} public void ejbRemove() {} } public void ejbStore(){} public void setEntityContext(EntityContext ctx){ this.entityContext = ctx; } public void unsetEntityContext(){ this.entityContext = null; } … 21/04/2017 © Robert Godin. Tous droits réservés. 87 Descripteur de déploiement XML <?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <enterprise-beans> <entity> <description>Entity Bean ( Container-managed Persistence )</description> <display-name>CompteEJB</display-name> <ejb-name>CompteEJB</ejb-name> <home>mypackage1.CompteEJBHome</home> <remote>mypackage1.CompteEJB</remote> <ejb-class>mypackage1.impl.CompteEJBBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>mypackage1.CompteEJBPK</prim-key-class> <reentrant>False</reentrant> 1.1//EN" <cmp-field> <field-name>solde</field-name> </cmp-field> <cmp-field> <field-name>noCompte</field-name> </cmp-field> <cmp-field> <field-name>noClient</field-name> </cmp-field> <cmp-field> <field-name>dateOuverture</field-name> </cmp-field> </entity> </enterprise-beans> </ejb-jar> 21/04/2017 © Robert Godin. Tous droits réservés. 88 Descripteur spécifique au conteneur Orion <?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE orion-ejb-jar PUBLIC "-//Evermind//DTD Enterprise JavaBeans 1.1 runtime//EN" "http://xmlns.oracle.com/ias/dtds/orion-ejb-jar.dtd"> <orion-ejb-jar> <enterprise-beans> <entity-deployment name="CompteEJB" copy-by-value="false" data-source="jdbc/ora9igodinDS" exclusive-writeaccess="false" table="COMPTE"> <primkey-mapping> <cmp-field-mapping> <fields> <cmp-field-mapping name="noCompte" persistence-name="NOCOMPTE" persistence-type="NUMBER(22)"/> </fields> </cmp-field-mapping> </primkey-mapping> <cmp-field-mapping name="solde" persistence-name="SOLDE" persistence-type="NUMBER(10,2)"/> <cmp-field-mapping name="noCompte"/> <cmp-field-mapping name="noClient"/> <cmp-field-mapping name="dateOuverture"/> </entity-deployment> </enterprise-beans> <assembly-descriptor> <default-method-access> <security-role-mapping name="&lt;default-ejb-caller-role>" impliesAll="true"/> </default-method-access> </assembly-descriptor> </orion-ejb-jar> 21/04/2017 © Robert Godin. Tous droits réservés. 89 Client du EJB package Samplemypackage1; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import mypackage1.*; import java.util.*; public class CompteEJBClient { public static void main(String [] args){ CompteEJBClient compteEJBClient = new CompteEJBClient(); try{ // Recherche de l'objet local home Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.rmi.RMIInitialContextFactory"); env.put(Context.SECURITY_PRINCIPAL, "admin"); env.put(Context.SECURITY_CREDENTIALS, "welcome"); env.put(Context.PROVIDER_URL, "ormi://localhost:23893/current-workspace-app"); Context ctx = new InitialContext(env); CompteEJBHome compteEJBHome = (CompteEJBHome)ctx.lookup("CompteEJB"); CompteEJB compteEJB; // Création d'un Compte Calendar maintenant = Calendar.getInstance(); // Calendrier avec date actuelle java.sql.Date dateMaintenant = new java.sql.Date(maintenant.getTime().getTime()); compteEJB = compteEJBHome.create(1000, dateMaintenant, 10); 21/04/2017 © Robert Godin. Tous droits réservés. 90 Client du EJB (suite) // Recherche de tous les Comptes Collection uneCollection = compteEJBHome.findAll(); Iterator unIterator = uneCollection.iterator(); System.out.println("Liste des comptes"); System.out.println("noCompte\tsolde\tdateOuverture\tnoClient"); while (unIterator.hasNext()){ compteEJB = (CompteEJB)unIterator.next(); System.out.println( compteEJB.getNoCompte()+"\t"+ compteEJB.getSolde()+"\t"+ compteEJB.getDateOuverture()+"\t"+ compteEJB.getNoClient()); } 21/04/2017 © Robert Godin. Tous droits réservés. 91 Client du EJB (suite) // Recherche des Comptes du Client 30 uneCollection = compteEJBHome.findByNoClient(30); unIterator = uneCollection.iterator(); System.out.println("Liste des comptes du client 30"); System.out.println("noCompte\tsolde\tdateOuverture\tnoClient"); while (unIterator.hasNext()){ compteEJB = (CompteEJB)unIterator.next(); System.out.println( compteEJB.getNoCompte()+"\t"+ compteEJB.getSolde()+"\t"+ compteEJB.getDateOuverture()+"\t"+ compteEJB.getNoClient()); } // Recherche du compte 200 compteEJB = compteEJBHome.findByPrimaryKey(new CompteEJBPK(200)); // Extraction du solde System.out.println("Solde du compte 200 :"+compteEJB.getSolde()); // Modification du solde compteEJB.setSolde(1000.0); // Suppression d'un Compte compteEJB = compteEJBHome.findByPrimaryKey(new CompteEJBPK(400)); compteEJB.remove(); } catch(Exception lException){ lException.printStackTrace(); } } } 21/04/2017 © Robert Godin. Tous droits réservés. 92 Façade EJB session accède au EJB entité … public class BanqueSessionCMDTEJB2Bean implements SessionBean { … public void transferer(int noCompteADebiter, int noCompteACrediter, double montant) throws EJBException{ // Créer l'objet EJB local (stub RMI) Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.rmi.RMIInitialContextFactory"); env.put(Context.SECURITY_PRINCIPAL, "admin"); env.put(Context.SECURITY_CREDENTIALS, "welcome"); env.put(Context.PROVIDER_URL, "ormi://localhost:23893/current-workspace-app"); try{ Context ctx = new InitialContext(env); CompteEJBHome compteEJBHome = (CompteEJBHome)ctx.lookup("CompteEJB"); CompteEJB compteADebiter = compteEJBHome.findByPrimaryKey(new CompteEJBPK(noCompteADebiter)); CompteEJB compteACrediter = compteEJBHome.findByPrimaryKey(new CompteEJBPK(noCompteACrediter)); // Appel de méthode sur l'objet EJB local compteADebiter.setSolde(compteADebiter.getSolde()-(long)montant); compteACrediter.setSolde(compteACrediter.getSolde()+(long)montant); } catch(Exception lException){ throw new EJBException(lException); } } } 21/04/2017 © Robert Godin. Tous droits réservés. 93 EJB 2 Interfaces locales pour accès léger – EJBLocalHome, EJBLocalObject Gestion des associations par le conteneur EJBQL pour la spécification des findXXX() 21/04/2017 © Robert Godin. Tous droits réservés. 94 EJB 3 Simplification de l’architecture EJB Annotation par métadonnées dans le code – – Mapping objet-relationnel Comportement transactionnel EJB codé comme une classe ordinaire (POJO) Java Persistence API (JPA) – – – 21/04/2017 interface Java indépendante de l’architecture (JSE ou JEE) test hors conteneur pas de home (cycle de vie géré par EntityManager) © Robert Godin. Tous droits réservés. 95 Exemple avec JDeveloppeur Classes EJB entités Editeur et Livre et annotations générées à partir des tables du schéma relationnel par le « wizard » Association un à plusieurs entre Editeur et Livre 21/04/2017 © Robert Godin. Tous droits réservés. 96 Classe Editeur package logiquemetier.persistance; import … Annotation @Entity pour EJB entité @Entity @Table(name="EDITEUR") public class Editeur implements Serializable { @Table pour correspondance private String nomediteur; private String ville; la classe et la table private Collection<Livre> livreCollection; public Editeur() { } entre public Editeur(String nomediteur, String ville) { this.nomediteur = nomediteur; this.ville = ville; }… 21/04/2017 © Robert Godin. Tous droits réservés. 97 Classe Editeur @Id pour clé primaire @Column pour correspondance avec colonne de la table (sur variable ou méthodes) package logiquemetier.persistance; … @Entity @Table(name="EDITEUR") public class Editeur implements Serializable { ... @Id @Column(name="NOMEDITEUR", primaryKey=true, nullable=false) public String getNomediteur() { return nomediteur; } public void setNomediteur(String nomediteur) { this.nomediteur = nomediteur; } @Column(name="VILLE", nullable=false) public String getVille() { return ville; } public void setVille(String ville) { this.ville = ville;} … 21/04/2017 © Robert Godin. Tous droits réservés. 98 Classe Editeur ... @OneToMany(targetEntity="logiquemetier.persistance.Livre") @JoinColumn(name="LIVRE.NOMEDITEUR", referencedColumnName="EDITEUR.NOMEDITEUR") public Collection<Livre> getLivreCollection() { return livreCollection; } public void setLivreCollection(Collection<Livre> livreCollection) { this.livreCollection = livreCollection;} public Livre addToLivreCollection(Livre livre) { getLivreCollection().add( livre ); livre.setEditeur( this ); return livre; } public Livre removeFromLivreCollection(Livre livre) { getLivreCollection().remove( livre ); livre.setEditeur( null ); return livre; } } 21/04/2017 © Robert Godin. Tous droits réservés. 99 Classe Livre package logiquemetier.persistance; import … @Entity @Table(name="LIVRE") public class Livre implements Serializable { private Long anneeparution; private String code; private String isbn; private String titre; private Editeur editeur; public Livre() { } public Livre(Long anneeparution, String code, String isbn, Editeur editeur, String titre) { this.anneeparution = anneeparution; this.code = code; this.isbn = isbn; this.editeur = editeur; this.titre = titre; } 21/04/2017 © Robert Godin. Tous droits réservés. 100 Classe Livre … @Column(name="ANNEEPARUTION", nullable=false) public Long getAnneeparution() { return anneeparution;} public void setAnneeparution(Long anneeparution) { this.anneeparution = anneeparution;} @Column(name="CODE", nullable=false) public String getCode() { return code;} public void setCode(String code) { this.code = code;} @Id @Column(name="ISBN", primaryKey=true, nullable=false) public String getIsbn() { return isbn;} public void setIsbn(String isbn) { this.isbn = isbn;} @Column(name="TITRE", nullable=false) public String getTitre() { return titre;} public void setTitre(String titre) { this.titre = titre;} 21/04/2017 © Robert Godin. Tous droits réservés. 101 Classe Livre … @ManyToOne(targetEntity="logiquemetier.persistance.Editeur") @JoinColumn(name="LIVRE.NOMEDITEUR", referencedColumnName="EDITEUR.NOMEDITEUR") public Editeur getEditeur() { return editeur; } public void setEditeur(Editeur editeur) { this.editeur = editeur; } } 21/04/2017 © Robert Godin. Tous droits réservés. 102 EJB session pour « CRUD » (généré par JDevelopper) Transactions gérées par le conteneur (mode REQUIRES NEW) package logiquemetier.facade; import … import logiquemetier.persistance.Editeur; import logiquemetier.persistance.Livre; @Remote public interface SessionFacadeEJB3 { List<Livre> findAllLivre() throws NamingException; Livre createLivre(Long anneeparution, String code, Editeur editeur, String isbn, String titre) throws NamingException; List<Editeur> findAllEditeur() throws NamingException; Editeur createEditeur(String nomediteur, String ville) throws NamingException; void updateEntity(Object entity) throws NamingException; void deleteEntity(Object entity) throws NamingException; void refreshEntity(Object entity) throws NamingException; } 21/04/2017 © Robert Godin. Tous droits réservés. 103 EJB session pour « CRUD » (généré par JDevelopper) package logiquemetier.facade; import … EntityManager est la classe JPA pour gérer les entités et transactions @Stateless(name="SessionFacadeEJB3") public class SessionFacadeEJB3Bean implements SessionFacadeEJB3 { private EntityManager _entityManager; public SessionFacadeEJB3Bean() { } public EntityManager getEntityManager() { return _entityManager; } @Resource : injection du EntityManager par le conteneur Associé à un persistence unit définis dans descripteur persistence.xml (paramètres connexion, …) @Resource public void setEntityManager(EntityManager entityManager) { _entityManager = entityManager; } 21/04/2017 © Robert Godin. Tous droits réservés. 104 EJB session pour « CRUD » (généré par JDevelopper) ... public List<Livre> findAllLivre() throws NamingException { return getEntityManager().createQuery("select object(o) from Livre o").getResultList(); } Langage de requête JPQL public Livre createLivre(Long anneeparution, String code, Editeur editeur, String isbn, String titre) throws NamingException { final Livre livre = new Livre(); Constructeur normal avec new livre.setAnneeparution(anneeparution); livre.setCode(code); livre.setEditeur(editeur); Appel à persist() rend l’objet persistant livre.setIsbn(isbn); Devient géré par le EntityManager : livre.setTitre(titre); contexte de persistance getEntityManager().persist(livre); return livre; } 21/04/2017 … © Robert Godin. Tous droits réservés. 105 EJB session pour « CRUD » (généré par JDevelopper) … public List<Editeur> findAllEditeur() throws NamingException { return getEntityManager().createQuery("select object(o) from Editeur o").getResultList(); } public Editeur createEditeur(String nomediteur, String ville) throws NamingException { final Editeur editeur = new Editeur(); editeur.setNomediteur(nomediteur); editeur.setVille(ville); getEntityManager().persist(editeur); return editeur; } merge() pour rattacher un objet au contexte de persistance remove() pour supprimer public void updateEntity(Object entity) throws NamingException { getEntityManager().merge( entity ); } public void deleteEntity(Object entity) throws NamingException { final EntityManager em = getEntityManager(); em.remove(em.merge(entity)); } public void refreshEntity(Object entity) throws NamingException { getEntityManager().refresh( entity ); } 21/04/2017 © Robert Godin. Tous droits réservés. } 106