TP1 EJB : mise en place de l'environnement, premières applications

De $1

Version de 23:06, 16 Avr 2024

cette version.

Revenir à liste des archives.

Voir la version actuelle

Installation logicielle

Logiciels obligatoires :

  • un JDK (pas un JRE) version 1.6 ou supérieur installé sur votre machine, pas un JRE. Si ce n'est pas le cas, récupérez sur http://java.sun.com/javase/downloads/index.jsp un jdk récent et installez-le. La version actuelle est la révision 21 du jdk 1.6.
  • Netbeans 6.9.x : Sur la page de download de netbeans 6 récupérez une version de netbeans qui vient avec les serveurs glassfish et tomcat (celle le plus à droite du tableau).

NOTE : les photos d'écrans sont toutes avec Glassfish v3 prelude et avec netbeans 6.7.1, comme nous allons utiliser glassfish v3.1 final et netbeans 6.9, il se peut qu'il y ait de petites différences.


NE CHANGEZ AUCUN MOT DE PASSE !

Note : si jamais les liens ci-dessus ont changé, google est votre ami pour récupérer des versions à jour.

Installez donc le JDK si vous ne l'avez pas, puis netbeans, et passons à la suite. JE CONSEILLE DE DESINSTALLER LA VERSION PRECEDENTE DE NETBEANS ET DE GLASSFISH SI VOUS AVIEZ DEJA UNE ANCIENNE VERSION !

Vérification de l'installation

Lancez netbeans une fois l'installation terminée. Cliquez sur l'onglet "services" et ouvrez l'item serveurs. Vous devez voir que tomcat et glassfish sont installés (si vous n'avez pas installé tomcat, ce n'est pas grave) :

 Snap1.jpg

Vérifions maintenant que tout est ok... L'archive que nous avons installée comprend l'outil de développement netbeans mais aussi le serveur d'application de Sun, appellé Glassfish. Lancez netbeans, puis allez dans l'onglet "services". Nous allons lancer le serveur pour voir si tout est ok:

Snap2bis.jpg

Puis, en demandant à voir la console d'administration, cela va ouvrir la page d'admin dans votre navigateur. En entrant le mot de passe adminadmin, une belle page devrait s'afficher. J'espère que vous n'avez pas modifié le mot de passe proposé par défaut, car dans une classe de TP, les statistiques prouvent que la moitié des élèves ne se souviennent plus de leur mot de passe très rapidement!

Snap3.jpg

 Snap4.jpg

 Le screenshot précédent présente la console d'administration. Nous en reparlerons plus tard. Le fait qu'elle s'affiche dans votre navigateur prouve que l'installation est ok.

Création d'un nouveau projet

Nous allons créer maintenant un projet pour le TP d'aujourd'hui. Allez dans le menu file/new project, puis dans la fenêtre suivante, sélectionnez JavaEE  comme catégorie de projet et « enterprise application » comme projet:

 Snap5.jpg

 Cliquez sur « next », puis donnez un nom à votre projet, cluquez sur next ou suivant...

Snap5bis.jpg

Indiquez maitenant que nous allons utiliser le serveur d'application de Sun (glassfish v3 ou v3.1 et le mode Java EE6)

 Snap7bis.jpg

Une fois terminé, vous verrez dans l'onglet project « trois sous projets » :

 Snap8bis.jpg

  1. Le premier correspond au projet « global » qui contient en fait les archives des deux autres (il correspond au fichier .ear du cours),
  2. le second correspond au .jar qui va contenir tous vos ejbs
  3. le troisième au .war qui va contenir vos servlets, jsps et pages html.

Le fichier .ear est en fait une archive qui contient les deux autres fichiers : le .jar et le .war, il permet de déployer l'ensemble de la web application d'un seul coup.

Nous verrons par la suite qu'on peut aussi mettre des EJBs dans une simple application web (nouveauté Java EE 6).

Création d'un premier ejb de type session bean stateless : HelloWorld

Allez sur le sous projet TP1-ejb (celui avec l'icone en forme de haricot, qui correspond à la partie "ejb" du projet) et faites bouton droit/new/session bean :

 Snap10.jpg

Puis dans la fenêtre suivante, indiquez le nom de votre Ejb, son type, et un nom de package (obligatoire) :

 Snap11.jpg

Une fois terminé, deux fichiers sont ajoutés dans le sous-projet : HelloWordBean.java et HelloWorldLocal.java, qui correspondent à la classe du bean et à son interface « locale » (un bean avec un interface locale ne peut être utilisé que par un client tournant dans la même JVM : une jsp, une servlet ou un autre ejb).

NOTE : si jamais on ne coche pas d'interface, ce qui n'est possible qu'avec un serveur Java EE6, cela suppose que le bean dispose par défaut d'une interface locale. Il est toujours nécessaire de créer explicitement une interface remote dans le cas d'une application distribuée.

Snap12.jpg

Ajoutons une méthode dans ce bean, la méthode getMessage() qui renvoie la chaîne de caractères «Hello World» :

public String getMessage() {
    System.out.println("Hello World qui va s'afficher dans la console du serveur, pour trace");
    return "Hello World";
}

Puis, dans l'éditeur, cliquez sur l'en-tête de la méthode, une petite lampe jaune doit appraître sur la gauche, proposant des actions contextuelles. Choisissez "Add to Local Interface" pour indiquer que cette méthode n'est pas une méthode interne du bean mais une méthode qui sera accessible au travers de l'interface locale du bean. Si jamais cette lampe n'apparait pas, il faut dans ce cas, manuellement, ajouter l'en-tête de la méthode dans l'interface.

Snap14.jpg

D'ailleurs, si vous regardez le source de HelloWorldLocal.java, vous verrez les lignes suivantes :

@Local
public interface HelloWorldLocal {
    java.lang.String getMessage();
}

Rappel : les lignes commençant par @ sont des « attributs de code » ou encore des "annotations de code". Ce sont en fait des méta-données… Elles serviront au compilateur à générer du code contextuel. Ceci permet de n'avoir dans le source .java que du POJO (Plain Old Java Object, une classe java "normale" en fin de compte !)

Ecriture d'une servlet client de test de ce bean

Sur le projet finissant par -war, faites click droit/new/Servlet :

Snap18.jpg

N'oubliez pas de donner un nom de package !

Snap19.jpg

Vous verrez que votre servlet est apparue dans les sources du projet. Dans un projet Java EE 6, les caractéristiques des servlets ne se trouvent plus dans un descripteur web.xml, mais directement dans le code source, sous la forme d'annotations de code. Ouvrez donc le code source de votre servlet et observez les annotations de code. La seconde indique l'URL auquel la servlet va répondre. Vous pouvez modifier directement le code pour modifier l'url, ou même utiliser des expressions régulières, par exemple : /Servlet* etc.

@WebServlet(name="ServletTest", urlPatterns={"/ServletTest"})
public class ServletTest extends HttpServlet {

Dans les sources de cette servlet, on va indiquer que l'on va « parler » au bean HelloWorld. Pour cela, cliquez avec le bouton de droite dans le code de la servlet (n'importe où) et selectionnez « insert code » :

Snap22.jpg

Un menu apparait, choisissez "call enterprise bean" :

Snap23.jpg

Cela va insérer dans la Servlet deux lignes de code qui vont correspondre à la fois à la déclaration et à l'initialisation d'une variable référençant un objet du type de l'interface du Bean que l'on souhaite appeler. Le @EJB est l'annotation de code qui va procéder à "l'injection de code" lors de la compilation ou du déploiement. Bien que la variable helloWorldBean ne soit pas initialisée, on pourra l'utiliser, elle ne vaudra pas "null" comme cela pourrait sembler :

public class ServletTest extends HttpServlet {
    @EJB
    private HelloWorldLocal helloWorldBean;

 

Notons aussi que quelques imports ont étés rajoutés, ce sont ceux dont le code généré aura besoin. L'objet helloWorldBean va, à partir de maintenant pouvoir être utilisé directement dans la servlet, sans que l'on ait besoin de l'initialiser.

Ajoutons la ligne suivantes dans la méthode processRequest de la servlet, après avoir décommenté les lignes de processRequest() :

out.println("L'ejb HelloWorld a renvoyé :" + helloWorldBean.getMessage());

Sauvegardons le tout, et exécutons le projet principal :

Snap25.jpg

Cela va avoir pour effet de lancer le serveur d'application, (cette opération peut prendre un certain temps, mais n'ayez crainte, si on fait plusieurs fois "run", si le serveur tourne déjà il ne sera pas relancé) des lignes vont s'afficher et au final, vous devriez avoir une ligne :

BUILD SUCCESSFUL (total time: 4 seconds)

Votre projet a été déployé et le navigateur doit ouvrir la page par défaut . Nous allons maintenant invoquer la servlet. Prenez un navigateur et tapez l'URL suivant : http://localhost:8080/TP1-war/ServletTest.

Normalement après un certain temps, le navigateur ouvre une page correspondant à la page JSP index.jsp home page du projet (dans "web pages", dans le fichier war, vous pourrez modifier le nom de la home page en éditant le fichier web.xml). Rajoutons juste l'URL relatif de la servlet à la fin : /ServletTest. Sur mon PC cela donne :

http://pc-de-michel:8080/TP1-war/ServletTest

 Snap11bis.jpg

Voilà, vous avez exécuté votre première appli web utilisant des EJBs !!!!

Pour ne pas se fatiguer, modifions la page par defaut et insérons un petit menu

La page par defaut est la page index.jsp du projet. Modifions là pour ajouter un menu avec une entrée qui permet de lancer la servlet facilement. Pour cela, double cliquez sur la page index.jsp dans le projet web. Elle se trouve dans le sous menu "web" du projet :

Snap12bis.jpg

Modifiez le source comme ceci :

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP accueil TP1</title>
    </head>
    <body>
        <h1>Menu du TP1</h1>
        <ul>
            <li><a href="ServletTest">Appeler la Servlet de test</a></li>
        </ul>
    </body>
</html>

Ecriture d’un second bean appelé par le bean HelloWorld

  • Procédez comme précédemment pour créer un second bean, semblable à HelloWorld, appelez-le MonSecondBean, ajoutez une méthode getMessage() qui renvoie « Je suis le second bean, appelé par le bean HelloWorld ».
  • Modifiez le bean HelloWorld afin que sa méthode getMessage() appelle la méthode getMessage() du second bean, récupère la valeur de retour et l’ajoute à la fin du message qu’elle renvoie.
  • Une fois vos modifications terminées, sauvegardez, déployez, testez la servlet.

Au final, lorsqu’on exécute la servlet de test, cela doit afficher : L'ejb HelloWorld a renvoyé : Hello World Je suis le second bean, appelé par le bean HelloWorld

 

Accès à une base de donnée via un entity bean et un persistence manager

La base de données, prise de contact

Sans aller trop loin, nous allons un peu jouer avec une base de données d'exemple livrée avec le JDK. Les outils dont vous disposez viennent en effet en standard avec une petite base de données d'exemple. Vous pouvez la consulter en cliquant sur l'onglet Services et en allant dans Databases/jdbc:derby//.../sample

Si jamais l'icône est cassée, faite clic droit/connect, le mot de passe est "app"...

 

Snap31.jpg

Dans cette base vous allez trouver les tables CUSTOMER, MANUFACTURER etc... en faisant click droit/view data sur les tables vous pouvez également consulter les données qui sont déjà disponibles.

Nous allons jouer pendant plusieurs TPs avec cette petite base de données.


Snap32.jpg

Création d'un entity bean proxy de la table MANUFACTURER

Ok, passons aux choses sérieuses, nous allons créer un entity bean proxy vers la table MANUFACTURER. Pour cela bouton droite sur le projet ejb et choisissez entity classes from database, dans la fenêtre qui s'ouvre choisissez jdbc/sample comme datasource et parmis les tables proposées choisissez MANUFACTURER.

Snap33.jpg

Cliquez sur next et n'oubliez pas en bas de la fenêtre de dialogue suivante, de cliquer le bouton create persistence unit, ça va avoir pour effet de créer un Persistence Manager pour le projet, soit en d'autres termes un module qui va gérer le mapping entre le monde des objets java et le monde des base de données relationnelles.

Snap34.jpg

Une fois le bouton cliqué on a une autre fenêtre qui apparait, permettant de choisir l'outil externe qui va assurer le mapping relationnel/objet. Par défaut c'est l'outil Toplink (autrefois payant et développé par Oracle) qui est proposé, mais si vous regardez le menu on peut aussi choisir l'outil très populaire Hibernate développé par JBoss. Gardez pour le moment les valeurs par défaut.

Snap38.jpg

On va choisir Create comme option, ce qui signifie que si besoin est on laissera le serveur d'application créer de nouvelles tables.

Cliquez sur bouton Create pour ajouter le persistence unit au projet. On peut voir qu'un projet persistence.xml a été rajouté au projet EJB (celui avec l'icone en forme de grain de café), sous l'entrée "Configuration files".

Regardez le projet ejb, un fichier Manufacturer.java a été créé, regardons le source :

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package tp1;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

/**
 *
 * @author michel
 */
@Entity
@Table(name = "MANUFACTURER")
@NamedQueries({@NamedQuery(name = "Manufacturer.findAll", query = "SELECT m FROM Manufacturer m"), @NamedQuery(name = "Manufacturer.findByManufacturerId", query = "SELECT m FROM Manufacturer m WHERE m.manufacturerId = :manufacturerId"), @NamedQuery(name = "Manufacturer.findByName", query = "SELECT m FROM Manufacturer m WHERE m.name = :name"), @NamedQuery(name = "Manufacturer.findByAddressline1", query = "SELECT m FROM Manufacturer m WHERE m.addressline1 = :addressline1"), @NamedQuery(name = "Manufacturer.findByAddressline2", query = "SELECT m FROM Manufacturer m WHERE m.addressline2 = :addressline2"), @NamedQuery(name = "Manufacturer.findByCity", query = "SELECT m FROM Manufacturer m WHERE m.city = :city"), @NamedQuery(name = "Manufacturer.findByState", query = "SELECT m FROM Manufacturer m WHERE m.state = :state"), @NamedQuery(name = "Manufacturer.findByZip", query = "SELECT m FROM Manufacturer m WHERE m.zip = :zip"), @NamedQuery(name = "Manufacturer.findByPhone", query = "SELECT m FROM Manufacturer m WHERE m.phone = :phone"), @NamedQuery(name = "Manufacturer.findByFax", query = "SELECT m FROM Manufacturer m WHERE m.fax = :fax"), @NamedQuery(name = "Manufacturer.findByEmail", query = "SELECT m FROM Manufacturer m WHERE m.email = :email"), @NamedQuery(name = "Manufacturer.findByRep", query = "SELECT m FROM Manufacturer m WHERE m.rep = :rep")})
public class Manufacturer implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "MANUFACTURER_ID")
    private Integer manufacturerId;
    @Column(name = "NAME")
    private String name;
    @Column(name = "ADDRESSLINE1")
    private String addressline1;
    @Column(name = "ADDRESSLINE2")
    private String addressline2;
    @Column(name = "CITY")
    private String city;
    @Column(name = "STATE")
    private String state;
    @Column(name = "ZIP")
    private String zip;
    @Column(name = "PHONE")
    private String phone;
    @Column(name = "FAX")
    private String fax;
    @Column(name = "EMAIL")
    private String email;
    @Column(name = "REP")
    private String rep;

    public Manufacturer() {
    }

    public Manufacturer(Integer manufacturerId) {
        this.manufacturerId = manufacturerId;
    }

    public Integer getManufacturerId() {
        return manufacturerId;
    }

Vous verrez que des méthodes permettant de rechercher des informations ont été automatiquement créées, que les attributs de la classe ont été associé (via des attributs de code) avec des colonnes, que la table sur laquelle se mappe ce bean est MANUFACTURER, etc... Remarquez les requêtes nommées qui ont été générées, notamment la requête "FindAll" qui correspond à un select * from MNUFACTURER en SQL :

@NamedQuery(name = "Manufacturer.findAll", query = "SELECT m FROM Manufacturer m")


L'URL de la base de données est connue du serveur d'application (pour en avoir le coeur net, ouvrez l'interface d'administration du serveur et allez voir dans Resources/JDBC/JDBC Resources/jdbc/sample vous verrez que le nom JNDI jdbc/sample est associé à un connexion pool appelé SamplePool, allez voir dans Resources/JDBC/Connection Pools/Sample Pools et vous verrez le détail de cette connexion.

Si vous avez besoin d'ajouter par exemple une base mySQL ou Postgres ou Oracle, il faudra utiliser ces outils d'administration pour configurer la connexion afin qu'elle soit connue du serveur d'application (il faudra également installer le .jar du driver jdbc dans le serveur d'application).

Modification du bean HelloWorld pour qu'il fasse un findAll sur les manufacturers

Ouvrez le source du bean HelloWorld. On va lui indiquer que nous allons utiliser le persistence manager que nous venons de créer pour utiliser des requêtes.

Pour cela: bouton de droite/persistence/use entity manager.

Snap36.jpg

Cela va insérer deux lignes dans le code de l'EJB : 

@Stateless
public class HelloWorldBean implements HelloWorldLocal {
    @PersistenceContext
    private EntityManager em;

Ces deux lignes déclarent que l'objet em est un entity manager et qu'il s'agit de l'entity manager par défaut. Dans un projet on peut avoir plusieurs persistence managers qui travaillent sur des bases de données différentes. Leurs caractéristiques se trouvent dans le fichier persistence.xml. Si on en a plusieurs, le premier est celui par defaut et il n'est pas la peine de le nommer. Si on veut indiquer que l'on utilise un persistence manager particulier on peut donner son nom, comme ici :

@Stateless
public class HelloWorldBean implements HelloWorldLocal {
    @PersistenceContext(name="TP1EJB_2009-ejbPU")
    private EntityManager em;

Au passage, vous pouvez voir le nom du persistance manager en cliquant sur le fichier persistence.xml dans le projet ejb :

Snap37.jpg

Pour le moment, ce n'est pas la peine de s'embêter, puisque nous n'en avons qu'un, laissons le code tel qu'il est par défaut (sans nom précisé).

 Ajoutez maintenant ce bout de code dans le bean :

Ajoutez ce bout de code dans le bean:

private void printAllManufacturers() {
    Query query = em.createNamedQuery("Manufacturer.findAll");
    Collection< Manufacturer > result = query.getResultList();

    for(Manufacturer cust:result ) {
        System.out.println(cust.getName());
    }
}

Cette méthode utilise l'entity manager pour exécuter la requête Manufacturer.findAll. Appelez cette méthode depuis la méthode getMessage(), sauvez, déployez, testez. Dans la console du serveur d'application (dans netbeans), vous devez avoir la liste des fournisseurs qui s'affiche :

Google
Sun MicroSystems
Acer
Matrox
3Com
CBX Cables
Sony
Getaway
SoftClip
Toshiba
Sams Publishing
Computer Cables & More
BMC
...

Affichage des résultats dans une page JSP

Nous allons modifier votre projet pour que la liste des manufacturers soit affichée dans une page Jsp une fois que l'on a cliqué sur le bouton de la page jsp initiale, au lieu de s'afficher dans la console avec des printlns...

En effet, les servlets ne sont pas faites pour afficher des résultats mais pour effectuer des traitements. L'endroit où l'on va écrire des résultats, proposer des formulaires, ce sont les pages JSP.

Nous allons donc modifier notre bean HelloWorld en ajoutant une méthode qui au lieu de faire des printlns avec les noms des manufacturers, va renvoyer une collection de Manufacturers:

Ajoutez donc cette méthode dans le bean HelloWorld:

public Collection  getAllManufacturers() {
        Query query = em.createNamedQuery("Manufacturer.findAll");
        Collection< Manufacturer > result = query.getResultList();
        
        return result;
  }


N'oubliez pas de dire qu'elle doit figurer dans l'interface locale du bean, afin de pouvoir être appelée depuis la servlet (lampe jaune à gauche ou bien copier/coller dans le fichier HelloWorldLocal.java)

Puis modifions la servlet :

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {       
        //out.println("L'ejb HelloWorld a renvoyé :" + helloWorldBean.getMessage());
        //out.close();

        Collection manufacturers = helloWorldBean.getAllManufacturers();

        request.setAttribute("Manufacturers", manufacturers);
        RequestDispatcher dp = request.getRequestDispatcher("DisplayManufacturers.jsp");
        dp.forward(request, response);
  }


La première ligne  appelle la méthode du bean et récupère en retour une Collection de manufacturers. Afin de la "faire passer à la JSP d'affichage", on la met dans la requête HTTP, à l'aide de la méthode setAttribute (seconde ligne en rouge).

Les deux dernières lignes "passent la main" à la JSP "DisplayManufacturers.jsp". La méthode forward permet de chainer des servlets et des JSPs en conservant le même objet request. Rappel : on a le droit de ne rien faire après un forward, surtout pas de générer un affichage.

La servlet ne peut rien afficher puisqu'elle passe la main juste après avoir fait un traitement.

Reste maintenant à écrire la JSP. Faire clic droit/New/JSP sur le projet war et nommer la JSP à créer pour qu'elle s'appelle DisplayManufacturers.jsp.

Puis modifier soncode ainsi :

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%--
The taglib directive below imports the JSTL library. If you uncomment it,
you must also add the JSTL library to the project. The Add Library... action
on Libraries node in Projects view can be used to add the JSTL 1.1 library.
--%>

<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>

    <body>
     <h1>List of Manufacturers</h1>
     <ul>
       <c:forEach var="u" items="${requestScope['Manufacturers']}">
	<li>${u.name}</li>
       </c:forEach>
    </ul>  
    </body>
</html>

La ligne <%@taglib... %> indique que nous allons utiliser une librairie de tag JSP particulière : la JSP Standard Tag Library (JSTL), livrée en standard avec J2EE. Cette librairie fournit des mécanismes pour manipuler les paramètres HTTP, etc... sans avoir besoin d'écrire du java.

La ligne avec le foreEach par exemple récupère un paramètre intitulé Manufacturers (ça tombe bien, c'est celui que nous avons passé depuis la servlet) et itère sur chaque élément (forEach...), puis on peut appeler les méthodes getName() etc... juste en les nommant, au travers de ce que l'on appelle "une variable JSP" : ${u.name}. Cette écriture est équivalent à un System.out.println(elem.getName()); ou elem est de type Manufacturer. C'est plus simple quand même ${u.name} que ce long println...

Ici, vous pourrez consulter une excellent tutorial sur la librairie JSTL  et sur le langage d'expression EL. Voir aussi le support de cours foruni sur la page principale.

Exécutez, cela doit afficher dans le navigateur la liste des Manufacturers.

Ajout d'un nouveau manufacturer


Nous allons maitenant utiliser le persistence manager pour ajouter une nouveau manufacturer dans la table. Le persistence manager va nous permettre de réaliser cette opération.

Modification de la classe Manufacturer.java pour que la clé primaire soit en auto-incrément :

Modifiez les lignes suivantes :

@Id
    @Column(name = "MANUFACTURER_ID", nullable = false)
    private Integer manufacturerId;

Modification du bean HelloWorld pour créer un nouveau manufacturer:

...
printAllManufacturers();

// On crée un nouveau customer
Manufacturer c = new Manufacturer();
c.setName("Michel Buffa");
// Nouvelle clé primaire
c.setManufacturerId(100);
em.persist(c);
System.out.println(################# APRES AJOUT ################);
printAllManufacturers();

Simple non ? La méthode persist permet d'ajouter un nouveau manufacturer dans la table.

Nous verrons que le persistence manager permet de faire des updates (méthode merge()), des delete, etc...