Skip to content

4. De webservice J2EE voor afspraken

Laten we terugkeren naar de architectuur van de te bouwen applicatie:

In dit deel richten we ons op de ontwikkeling van de webservice J2EE [1] die wordt uitgevoerd op een Sun/Glassfish-server.

4.1. De database

De database, die we [dbrdvmedecins] zullen noemen, is een MySQL5-database met vier tabellen:

Image

4.1.1. De tabel [MEDECINS]

Deze bevat informatie over de artsen die worden beheerd door de applicatie [RdvMedecins].

  • ID: identificatienummer van de arts – primaire sleutel van de tabel
  • VERSION: identificatienummer van de versie van de rij in de tabel. Dit nummer wordt telkens met 1 verhoogd wanneer er een wijziging in de rij wordt aangebracht.
  • NOM: de achternaam van de arts
  • PRENOM: zijn of haar voornaam
  • TITRE: zijn/haar aanspreektitel (mevrouw, meisje, meneer)

4.1.2. De tabel [CLIENTS]

De cliënten van de verschillende artsen worden opgeslagen in de tabel [CLIENTS]:

  • ID: identificatienummer van de klant – primaire sleutel van de tabel
  • VERSION: nummer dat de versie van de rij in de tabel identificeert. Dit nummer wordt met 1 verhoogd telkens wanneer er een wijziging in de rij wordt aangebracht.
  • NOM: de naam van de klant
  • PRENOM: de voornaam
  • TITRE: zijn/haar aanspreektitel (mevrouw, meisje, heer)

4.1.3. De tabel [CRENEAUX]

Deze tabel geeft een overzicht van de tijdvakken waarin de RV mogelijk zijn:

  • ID: nummer dat het tijdvak identificeert – primaire sleutel van de tabel (regel 8)
  • VERSION: nummer dat de versie van de rij in de tabel identificeert. Dit nummer wordt met 1 verhoogd telkens wanneer er een wijziging in de rij wordt aangebracht.
  • ID_MEDECIN: nummer dat de arts identificeert aan wie dit tijdslot toebehoort – vreemde sleutel op de kolom MEDECINS (ID).
  • HDEBUT: starttijd van het tijdvak
  • MDEBUT: minuten begin van het tijdvak
  • HFIN: einduur van het tijdvak
  • MFIN: minuten einde tijdslot

De tweede regel van de tabel [CRENEAUX] (zie [1] hierboven) geeft bijvoorbeeld aan dat tijdvak nr. 2 om 8.20 uur begint en om 8.40 uur eindigt en toebehoort aan arts nr. 1 (mevrouw Marie PELISSIER).

4.1.4. De tabel [RV]

Deze tabel geeft een overzicht van de RV die voor elke arts zijn vastgelegd:

  • ID: nummer dat de RV op unieke wijze identificeert – primaire sleutel
  • JOUR: dag van de RV
  • ID_CRENEAU: tijdvak van RV – externe sleutel op het veld [ID] van de tabel [CRENEAUX] – bepaalt zowel het tijdvak als de betreffende arts.
  • ID_CLIENT: nummer van de klant voor wie de reservering is gemaakt – externe sleutel op het veld [ID] van de tabel [CLIENTS]

Deze tabel heeft een uniekheids -beperking op de waarden van de gekoppelde kolommen (JOUR, ID_CRENEAU):

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

Als een rij in de tabel [RV] de waarde (JOUR1, ID_CRENEAU1) voor de kolommen (JOUR, ID_CRENEAU), dan mag deze waarde nergens anders voorkomen. Anders zou dit betekenen dat er twee RV’en tegelijkertijd voor dezelfde arts zijn vastgelegd. Vanuit het oogpunt van Java-programmering start de JDBC-driver van de database een SQLException wanneer dit zich voordoet.

De regel met id gelijk aan 3 (zie [1] hierboven) betekent dat er op 23/08/2006 een RV is geboekt voor tijdvak nr. 20 en klant nr. 4. Uit de tabel [CRENEAUX] blijkt dat tijdvak nr. 20 overeenkomt met het tijdvak 16.20 - 16.40 uur en toebehoort aan arts nr. 1 (mevrouw Marie PELISSIER). Uit de tabel [CLIENTS] blijkt dat klant nr. 4 mevrouw Brigitte BISTROU is.

4.2. Aanmaken van de database

Maak de database MySql [dbrdvmedecins] aan met een tool naar keuze. Om de tabellen aan te maken en te vullen, kunt u het script [createbd.sql] gebruiken dat u wordt verstrekt. De inhoud ervan is als volgt:

create table CLIENTS (
        ID bigint not null auto_increment,
        VERSION integer not null,
        TITRE varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(30) not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table CRENEAUX (
        ID bigint not null auto_increment,
        VERSION integer not null,
        HDEBUT integer not null,
        MDEBUT integer not null,
        HFIN integer not null,
        MFIN integer not null,
        ID_MEDECIN bigint not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table MEDECINS (
        ID bigint not null auto_increment,
        VERSION integer not null,
        TITRE varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(30) not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table RV (
        ID bigint not null auto_increment,
        JOUR date not null,
        ID_CLIENT bigint not null,
        ID_CRENEAU bigint not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    alter table CRENEAUX 
        add index FK9BD7A197FE16862 (ID_MEDECIN), 
        add constraint FK9BD7A197FE16862 
        foreign key (ID_MEDECIN) 
        references MEDECINS (ID);

    alter table RV 
        add index FKA4494D97AD2 (ID_CLIENT), 
        add constraint FKA4494D97AD2 
        foreign key (ID_CLIENT) 
        references CLIENTS (ID);

    alter table RV 
        add index FKA441A673246 (ID_CRENEAU), 
        add constraint FKA441A673246 
        foreign key (ID_CRENEAU) 
        references CRENEAUX (ID);

INSERT INTO CLIENTS ( VERSION, NOM, PRENOM, TITRE) VALUES (1, 'MARTIN', 'Jules', 'Mr');
...

INSERT INTO MEDECINS ( VERSION, NOM, PRENOM, TITRE) VALUES (1, 'PELISSIER', 'Marie', 'Mme');
...

INSERT INTO CRENEAUX ( VERSION, ID_MEDECIN, HDEBUT, MDEBUT, HFIN, MFIN) VALUES (1, 1, 8, 0, 8, 20);
...

INSERT INTO RV ( JOUR, ID_CRENEAU, ID_CLIENT) VALUES ('2006-08-22', 1, 2);
...

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

COMMIT WORK;

4.3. De elementen van de serverzijde-architectuur

Laten we terugkeren naar de architectuur van de te bouwen applicatie:

Aan de serverzijde zal de applicatie bestaan uit:

  1. een JPA-laag waarmee met de BD kan worden gewerkt door middel van objecten
  1. een EJB die verantwoordelijk is voor het beheer van de bewerkingen met de JPA-laag
  2. een webservice die de interface van de EJB in de vorm van een webservice beschikbaar stelt aan externe clients.

De elementen (b) en (c) implementeren de [dao]-laag die in het vorige schema is weergegeven. Het is bekend dat een applicatie toegang kan krijgen tot een externe EJB via de protocollen RMI en JNDI. In de praktijk beperkt dit de clients tot Java-clients. Een webservice maakt gebruik van een gestandaardiseerd communicatieprotocol dat door verschillende talen wordt geïmplementeerd: .NET, PHP, C++, ... Dit willen we hier laten zien aan de hand van een .NET-client.

Voor een korte inleiding tot webservices kunt u de cursus [ref1] raadplegen, paragraaf 14, pagina 109.

Een webservice kan op twee manieren worden geïmplementeerd:

  • door een klasse met de annotatie @WebService die in een webcontainer wordt uitgevoerd
  • door een EJB met de annotatie @WebService die wordt uitgevoerd in een EJB-container

We gaan hier de eerste oplossing gebruiken:

In de cursus [ref1], paragraaf 14, pagina 109, staat een voorbeeld waarin de tweede oplossing wordt gebruikt.

4.4. Hibernate- configuratie van de GlassFish-server

Afhankelijk van de versie beschikt de Glassfish-server V2 die bij NetBeans wordt geleverd mogelijk niet over de Hibernate-bibliotheken die de JPA/Hibernate-laag nodig heeft. Als u tijdens het doorlopen van de tutorial ontdekt dat Glassfish geen JPA/Hibernate-implementatie aanbiedt of dat er bij het implementeren van de services een uitzondering wordt gemeld dat de Hibernate-bibliotheken niet worden gevonden, moet u de bibliotheken toevoegen aan de map [<glassfish>/domains/domain1/lib/ext] en vervolgens de Glassfish-server opnieuw opstarten:

  • in [1], de map <glassfish>/.../lib/ext
  • in [2], de Hibernate-bibliotheken plus enkele JDBC-drivers
  • in [3], de JDBC-driver van MySQL

De Hibernate-bibliotheken zitten in de zip-bestand dat bij de tutorial hoort.

4.5. De tools voor automatische generatie van NetBeans

Laten we teruggaan naar de architectuur die we moeten bouwen:

Met NetBeans is het mogelijk om automatisch de laag [JPA] en de laag [Ejb] te genereren, die de toegang tot de gegenereerde entiteiten JPA regelen. Het is interessant om deze methoden voor automatische generatie te kennen, omdat de gegenereerde code waardevolle aanwijzingen geeft over hoe JPA-entiteiten of de EJB-code die deze gebruikt, moeten worden geschreven.

We beschrijven nu enkele van deze automatische generatietools. Om de gegenereerde code te begrijpen, moet men een goede kennis hebben van de entiteiten JPA, [ref1] en de EJB, [ref2].

Een NetBeans-verbinding met de database maken

  • Start de entiteiten SGBD en MySQL 5 op, zodat de entiteit BD beschikbaar is
  • Maak een NetBeans-verbinding met de database [dbrdvmedecins]
  • in het tabblad [Files], in de tak [Databases] [1], selecteer de JDBC-driver MySQL [2]
  • en selecteer vervolgens de optie [3] "Connect Using" waarmee u een verbinding kunt maken met een database MySQL
  • in [4], voer de gevraagde gegevens in
  • en bevestig vervolgens in [5]
  • in [6] is de verbinding tot stand gebracht. Hier ziet u de vier tabellen van de database waarmee verbinding is gemaakt.

Een EJB-project aanmaken

  • in [1], maak een nieuwe applicatie aan, een EJB-module
  • in [2], de categorie [Java EE] selecteren en in [3] het type [EJB Module]
  • in [4] een map voor het project kiezen en in [5] het een naam geven – en vervolgens de wizard afsluiten
  • in [6] het gegenereerde project

Een resource JDBC toevoegen aan de Glassfish-server

We gaan een bron JDBC toevoegen aan de Glassfish-server.

  • in het tabblad [Services] de Glassfish-server [2, 3] starten
  • klik in het tabblad [Projects] met de rechtermuisknop op het EJB-project en selecteer in [5] de optie [New / Other] waarmee u een element aan het project kunt toevoegen.

Image

  • in [6], selecteer de categorie [Glassfish] en geef in [7] aan dat je een resource JDBC wilt aanmaken door het type [JDBC Resource] te selecteren
  • in [8] aangeven dat deze resource JDBC zijn eigen verbindingspool gaat gebruiken
  • in [9], geef de resource JDBC een naam
  • in [10], ga naar de volgende stap
  • in [11] worden de kenmerken van de verbindingspool van de resource JDBC gedefinieerd
  • in [12], geef de verbindingspool een naam
  • in [13], kies de eerder aangemaakte NetBeans-verbinding [dbrdvmedecins]
  • in [14], ga naar de volgende stap
  • in [15] hoeft er normaal gesproken niets te worden gewijzigd op deze pagina. De eigenschappen van de databaseverbinding MySQL [dbrdvmedecins] zijn overgenomen van die van de eerder aangemaakte NetBeans-verbinding [dbrdvmedecins]
  • in [16], ga naar de volgende stap
  • naar [17], behoud de voorgestelde standaardwaarden
  • naar [18], sluit de wizard af. Deze maakt het bestand [sun-resources.xml] [19] aan met de volgende inhoud:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins" object-type="user" pool-name="dbrdvmedecinsPool">
    <description/>
  </jdbc-resource>
  <jdbc-connection-pool ...">
    <property name="URL" value="jdbc:mysql://localhost:3306/dbrdvmedecins"/>
    <property name="User" value="root"/>
    <property name="Password" value="()"/>
  </jdbc-connection-pool>
</resources>

Het bovenstaande bestand bevat alle informatie die in de wizard is ingevoerd in het formaat XML. Het wordt door NetBeans gebruikt om de GlassFish-server te vragen de in regel 4 gedefinieerde bron "jdbc/dbrdvmedecins" aan te maken.

Een persistentie-eenheid aanmaken

De persistentie-eenheid [persistence.xml] configureert de laag JPA: deze geeft de gebruikte implementatie JPA aan (Toplink, Hibernate, ...) en configureert deze.

  • in [1], klik met de rechtermuisknop op het EJB-project en selecteer [New / Other] in [2]
  • in [3], selecteer de categorie [Persistence] en vervolgens in [4], geef aan dat u een persistentie-eenheid wilt aanmaken JPA
  • in [5], geef de aangemaakte persistentie-eenheid een naam
  • in [6], kies [Hibernate] als implementatie JPA
  • in [7], selecteer de zojuist aangemaakte Glassfish-resource "jdbc/dbrdvmedecins"
  • in [8], geef aan dat er geen actie op de database moet worden uitgevoerd bij het instantiëren van de laag JPA
  • sluit de wizard af
  • in [9]: het door de wizard aangemaakte bestand [persistence.xml]

De inhoud ervan is als volgt:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="serveur-ejb-dao-jpa-hibernate-generePU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties/>
  </persistence-unit>
</persistence>

Ook hier worden de in de wizard opgegeven gegevens weergegeven in het formaat XML. Dit bestand volstaat niet om te werken met de database MySQL5 "dbrdvmedecins". We zouden aan Hibernate moeten aangeven welk type SGBD moet worden beheerd. Dit zal later worden gedaan.

Aanmaken van de entiteiten JPA

 
  • in [1], klik met de rechtermuisknop op het project en in [2] kies de optie [New / Other]
  • in [3], selecteer de categorie [Persistence] en geef vervolgens in [4] aan dat u entiteiten JPA wilt aanmaken op basis van een bestaande database.
  • in [5] de bron JDBC "jdbc/dbrdvmedecins" selecteren die we hebben aangemaakt
  • in [6], de vier tabellen van de bijbehorende database
  • in [7,8]: neem ze allemaal op in de generatie van entiteiten in JPA
  • in [9], de wizard voortzetten
  • in [10]: de entiteiten JPA die zullen worden gegenereerd
  • in [11], geef het pakket van de entiteiten JPA een naam
  • in [12], kies het Java-type dat de lijsten met objecten van de laag JPA zal inkapselen
  • sluit de wizard af
  • in [13], de vier gegenereerde entiteiten JPA, één voor elke tabel in de database.

Hieronder staat bijvoorbeeld de code van de entiteit [Rv], die een rij van de tabel [rv] uit de database [dbrdvmedecins] vertegenwoordigt.

package jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  @Basic(optional = false)
  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;
  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneaux idCreneau;
  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Clients idClient;

  public Rv() {
  }

...
}

Aanmaken van de EJB-toegangslaag voor de entiteiten JPA

  • in [1], klik met de rechtermuisknop op het project en in [2], selecteer de optie [New / Other]
  • in [3], selecteer de categorie [Persistence] en vervolgens in [4] het type [Session Beans for Entity Classes]
  • in [5] worden de eerder aangemaakte entiteiten JPA weergegeven
  • in [6], selecteer ze allemaal
  • in [7] zijn ze geselecteerd
  • in [8], ga verder met de wizard
  • in [9], geef een naam aan het pakket met de EJB’s die zullen worden gegenereerd
  • in [10], geef aan dat de EJB’s zowel een lokale als een externe interface moeten implementeren
  • de wizard afsluiten
  • in [11], de gegenereerde EJB's

Hier is bijvoorbeeld de code van de EJB die de toegang tot de entiteit [Rv] beheert, dus tot de tabel [rv] in de database [dbrdvmedecins]:

package ejb;
...
@Stateless
public class RvFacade implements RvFacadeLocal, RvFacadeRemote {
  @PersistenceContext
  private EntityManager em;

  public void create(Rv rv) {
    em.persist(rv);
  }

  public void edit(Rv rv) {
    em.merge(rv);
  }

  public void remove(Rv rv) {
    em.remove(em.merge(rv));
  }

  public Rv find(Object id) {
    return em.find(Rv.class, id);
  }

  public List<Rv> findAll() {
    return em.createQuery("select object(o) from Rv as o").getResultList();
  }

}

Zoals gezegd kan het automatisch genereren van code erg nuttig zijn om een project op te starten en vertrouwd te raken met de entiteiten JPA en EJB. Verderop herschrijven we de lagen JPA en EJB met onze eigen code, maar de lezer zal daar informatie terugvinden die we zojuist hebben besproken bij het automatisch genereren van de lagen.

4.6. Het NetBeans-project van de EJB-module

We maken een nieuwe, lege EJB-module aan (zie paragraaf 4.5):

 
  • het pakket [rdvmedecins.entites] bevat de entiteiten van de JPA-laag
  • het pakket [rdvmedecins.dao] implementeert de EJB van de laag [dao]
  • het pakket [rdvmedecins.exceptions] implementeert een toepassingsspecifieke uitzonderingsklasse

Hierna gaan we ervan uit dat de lezer alle stappen uit paragraaf 4.5 heeft gevolgd. Sommige daarvan zal hij moeten herhalen.

4.6.1. Configuratie van de laag JPA

Laten we nog eens de architectuur van onze client/server-toepassing in herinnering brengen:

Het NetBeans-project:

 

De laag [JPA] wordt geconfigureerd door de hierboven genoemde bestanden [persistence.xml] en [sun-resources.xml]. Deze twee bestanden worden gegenereerd door wizards die we al eerder hebben gezien:

  • het genereren van het bestand [sun-resources.xml] is beschreven in paragraaf 4.5.
  • het genereren van het bestand [persistence.xml] is beschreven in paragraaf 4.5.

Het gegenereerde bestand [persistence.xml] moet als volgt worden aangepast:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbrdvmedecins" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <properties>
       <!-- Dialect -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>
  • regel 3: het transactietype is JTA: de transacties worden beheerd door de EJB3-container van Glassfish
  • regel 4: er wordt een JPA/Hibernate-implementatie gebruikt. Hiervoor is de Hibernate-bibliotheek toegevoegd aan de GlassFish-server (zie paragraaf 4.4).
  • regel 5: de gegevensbron JTA die door de JPA-laag wordt gebruikt, heeft de naam JNDI „jdbc/dbrdvmedecins”.
  • regel 8: deze regel wordt niet automatisch gegenereerd. Deze moet handmatig worden toegevoegd. Deze geeft aan Hibernate door dat de gebruikte SGBD de MySQL5 is.

De gegevensbron "jdbc/dbrdvmedecins" is geconfigureerd in het volgende bestand [sun-resources.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins" object-type="user" pool-name="dbrdvmedecinsPool">
    <description/>
  </jdbc-resource>
  <jdbc-connection-pool ...>
    <property name="URL" value="jdbc:mysql://localhost/dbrdvmedecins"/>
    <property name="User" value="root"/>
    <property name="Password" value="()"/>
  </jdbc-connection-pool>
</resources>
  • regels 8-10: de JDBC-kenmerken van de gegevensbron (database-URL, gebruikersnaam en wachtwoord). De database MySQL dbrdvmedecins is de database die in paragraaf 4.1 wordt beschreven.
  • regel 7: de kenmerken van de verbindingspool die aan deze gegevensbron is gekoppeld

4.6.2. De entiteiten van de laag JPA

Laten we nog eens de architectuur van onze client/server-toepassing in herinnering brengen:

Het NetBeans-project:

Het pakket [rdvmedecins.entites] implementeert de laag [Jpa].

In paragraaf 4.5 hebben we gezien hoe we automatisch JPA-entiteiten voor een applicatie kunnen genereren. We zullen deze techniek hier niet gebruiken, maar de entiteiten zelf definiëren. Deze zullen echter een groot deel van de in paragraaf 4.5 gegenereerde code overnemen. Hier willen we dat de entiteiten [Medecin] en [Client] dochterklassen zijn van de klasse [Personne].

De klasse Personne wordt gebruikt om artsen en klanten weer te geven:

package rdvmedecins.entites;
...
@MappedSuperclass
public class Personne implements Serializable {
   // kenmerken van een persoon

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Version
  @Column(name = "VERSION", nullable = false)
  private Integer version;

  @Column(name = "TITRE", length = 5, nullable = false)
  private String titre;
  @Column(name = "NOM", length = 30, nullable = false)
  private String nom;
  @Column(name = "PRENOM", length = 30, nullable = false)
  private String prenom;

   // standaardconstructor
  public Personne() {
  }

   // fabrikant met parameters
  public Personne(String titre, String nom, String prenom) {
     // we gebruiken de setters
...
  }

   // constructor via kopiëren
  public Personne(Personne personne) {
     // via setters
 ...
  }

   // toString
  @Override
  public String toString() {
    return "[" + titre + "," + prenom + "," + nom + "]";
  }

// getters en setters
....
}
  • regel 3: merk op dat de klasse [Personne] zelf geen entiteit (@Entity) is. Deze klasse zal de bovenliggende klasse van entiteiten zijn. De annotatie @MappedSuperClass duidt deze situatie aan.

De entiteit [Client] omvat de rijen van de tabel [clients]. Deze is afgeleid van de voorgaande klasse [Personne]:

package rdvmedecins.entites;
....
@Entity
@Table(name = "CLIENTS")
public class Client extends Personne implements Serializable {

   // standaardconstructor
  public Client() {
  }

   // constructor met parameters
  public Client(String titre, String nom, String prenom) {
     // ouder
    super(titre, nom, prenom);
  }

   // kopieerconstructor
  public Client(Client client) {
     // bovenliggend
    super(client);
  }
}
  • regel 3: de klasse [Client] is een JPA-entiteit
  • regel 4: deze is gekoppeld aan de tabel [clients]
  • regel 5: deze is afgeleid van de klasse [Personne]

De entiteit [Medecin], die de rijen van de tabel [medecins] omvat, volgt hetzelfde patroon:

package rdvmedecins.entites;
...
@Entity
@Table(name = "MEDECINS")
public class Medecin extends Personne implements Serializable {

   // standaardconstructor
  public Medecin() {
  }

   // constructor met parameters
  public Medecin(String titre, String nom, String prenom) {
     // bovenliggend
    super(titre, nom, prenom);
  }

   // kopieerconstructor
  public Medecin(Medecin medecin) {
     // bovenliggend
    super(medecin);
  }
}

De entiteit [Creneau] omvat de rijen van de tabel [creneaux]:

package rdvmedecins.entites;
....
@Entity
@Table(name = "CRENEAUX")
public class Creneau implements Serializable {

   // kenmerken van een tijdslot van RV
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Version
  @Column(name = "VERSION", nullable = false)
  private Integer version;
  @ManyToOne
  @JoinColumn(name = "ID_MEDECIN", nullable = false)
  private Medecin medecin;
  @Column(name = "HDEBUT", nullable = false)
  private Integer hdebut;
  @Column(name = "MDEBUT", nullable = false)
  private Integer mdebut;
  @Column(name = "HFIN", nullable = false)
  private Integer hfin;
  @Column(name = "MFIN", nullable = false)
  private Integer mfin;

   // standaardfabrikant
  public Creneau() {

  }

   // constructor met parameters
  public Creneau(Medecin medecin, Integer hDebut,Integer mDebut, Integer hFin, Integer mFin) {
     // we gebruiken de setters
...
  }

   // constructor via kopiëren
  public Creneau(Creneau creneau) {
     // via setters
...
  }

   // toString
  @Override
  public String toString() {
    return "[" + getId() + "," + getVersion() + "," + getMedecin() + "," + getHdebut() + ":" + getMdebut() + "," + getHfin() + ":" + getMfin() + "]";
  }

   // setters - getters
...
}
  • de rijen 15-17 modelleren de "één-op-veel"-relatie die bestaat tussen de tabel [creneaux] en de tabel [medecins] in de database.

De entiteit [Rv] omvat de rijen van de tabel [rv]:

package rdvmedecins.entites;
...
@Entity
@Table(name = "RV")
public class Rv implements Serializable {
   // eigenschappen

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Column(name = "JOUR", nullable = false)
  @Temporal(TemporalType.DATE)
  private Date jour;
  @ManyToOne
  @JoinColumn(name = "ID_CLIENT", nullable = false)
  private Client client;
  @ManyToOne
  @JoinColumn(name = "ID_CRENEAU", nullable = false)
  private Creneau creneau;

   // standaardconstructor
  public Rv() {
  }

   // constructor met parameters
  public Rv(Date jour, Client client, Creneau creneau) {
     // we gebruiken de setters
...
  }

   // constructor via kopiëren
  public Rv(Rv rv) {
     // via setters
...
  }

   // toString
  @Override
  public String toString() {
    return "[" + getId() + "," + new SimpleDateFormat("dd/MM/yyyy").format(getJour()) + "," + getClient() + "," + getCreneau() + "]";
  }

// getters en setters
...
}
  • de rijen 15-17 modelleren de „één-op-veel”-relatie die bestaat tussen de tabel [rv] en de tabel [clients] in de database, en de rijen 18-20 de "één-op-veel"-relatie die bestaat tussen de tabel [rv] en de tabel [creneaux]

4.6.3. De uitzonderingsklasse

De uitzonderingsklasse [RdvMedecinsException] van de applicatie is als volgt:

package rdvmedecins.exceptions;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class RdvMedecinsException extends RuntimeException {

  private static final long serialVersionUID = 1L;

   // privévelden
  private int code = 0;

   // constructors
  public RdvMedecinsException() {
    super();
  }

  public RdvMedecinsException(String message) {
    super(message);
  }

  public RdvMedecinsException(String message, Throwable cause) {
    super(message, cause);
  }

  public RdvMedecinsException(Throwable cause) {
    super(cause);
  }

  public RdvMedecinsException(String message, int code) {
    super(message);
    setCode(code);
  }

  public RdvMedecinsException(Throwable cause, int code) {
    super(cause);
    setCode(code);
  }

  public RdvMedecinsException(String message, Throwable cause, int code) {
    super(message, cause);
    setCode(code);
  }

   // getters - setters
...
}
  • regel 6: de klasse is afgeleid van de klasse [RuntimeException]. De compiler dwingt dus niet om deze met try/catch te behandelen.
  • regel 5: de annotatie @ApplicationException zorgt ervoor dat de uitzondering niet wordt ‘opgeslokt’ door een uitzondering van het type [EjbException].

Om de annotatie @ApplicationException te begrijpen, gaan we terug naar de architectuur die aan de serverzijde wordt gebruikt:

De uitzondering van het type [RdvMedecinsException] wordt gegenereerd door de EJB-methoden van de laag [dao] binnen de EJB3-container en door deze opgevangen. Zonder de annotatie @ApplicationException kapselt de EJB3-container de opgetreden uitzondering in een uitzondering van het type [EjbException] in en gooit deze opnieuw. Het kan zijn dat men deze inkapseling niet wenst en een uitzondering van het type [RdvMedecinsException] uit de Ejb3-container wil laten ontsnappen. Dit wordt mogelijk gemaakt door de annotatie @ApplicationException. Bovendien geeft het attribuut (rollback=true) van deze annotatie aan de EJB3-container aan dat, als de uitzondering van het type [RdvMedecinsException] optreedt binnen een methode die wordt uitgevoerd in een transactie met een SGBD, deze transactie moet worden teruggedraaid. In technische termen wordt dit een rollback van de transactie uitvoeren genoemd.

4.6.4. De EJB van de laag [dao]

De Java-interface [IDao] van de laag [dao] is als volgt:

package rdvmedecins.dao;
...
public interface IDao {

   // klantenlijst
  public List<Client> getAllClients();
   // lijst met artsen
  public List<Medecin> getAllMedecins();
   // lijst met tijdvakken van een arts
  public List<Creneau> getAllCreneaux(Medecin medecin);
   // lijst met afspraken van een arts op een bepaalde dag
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // een klant zoeken op basis van zijn ID
  public Client getClientById(Long id);
   // een klant zoeken op basis van zijn ID
  public Medecin getMedecinById(Long id);
   // een afspraak zoeken op basis van de id
  public Rv getRvById(Long id);
   // een tijdslot zoeken op basis van de id
  public Creneau getCreneauById(Long id);
   // een RV toevoegen
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // een RV verwijderen
  public void supprimerRv(Rv rv);
}

De lokale interface [IDaoLocal] van de EJB is slechts een afgeleide van de voorgaande interface [IDao]:

1
2
3
4
5
6
7
package rdvmedecins.dao;

import javax.ejb.Local;

@Local
public interface IDaoLocal extends IDao{
}

Hetzelfde geldt voor de externe interface [IDaoRemote]:

1
2
3
4
5
6
7
package rdvmedecins.dao;

import javax.ejb.Remote;

@Remote
public interface IDaoRemote extends IDao {
}

De EJB [DaoJpa] implementeert beide interfaces, zowel de lokale als de externe:

1
2
3
4
5
6
7
package rdvmedecins.dao;
...
@Stateless(mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal,IDaoRemote {
...
}
  • regel 3 geeft aan dat de remote EJB de naam "rdvmedecins.dao" draagt
  • regel 4 geeft aan dat alle methoden van de EJB plaatsvinden binnen een transactie die wordt beheerd door de EJB3-container.
  • regel 5 laat zien dat de EJB de lokale en de remote-interface implementeert.

De volledige code van de EJB is als volgt:

package rdvmedecins.dao;
...
@Stateless(mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal,IDaoRemote {

  @PersistenceContext
  private EntityManager em;

   // lijst met klanten
  public List<Client> getAllClients() {
    try {
      return em.createQuery("select c from Client c").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 1);
    }
  }

   // lijst met artsen
  public List<Medecin> getAllMedecins() {
    try {
      return em.createQuery("select m from Medecin m").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 2);
    }
  }

   // lijst met beschikbare tijdvakken van een bepaalde arts
   // arts: de arts
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    try {
      return em.createQuery("select c from Creneau c join c.medecin m where m.id=:idMedecin").setParameter("idMedecin", medecin.getId()).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 3);
    }
  }

   // lijst met afspraken van een bepaalde arts op een bepaalde dag
   // arts: de arts
   // dag: de dag
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    try {
      return em.createQuery("select rv from Rv rv join rv.creneau c join c.medecin m where m.id=:idMedecin and rv.jour=:jour").setParameter("idMedecin", medecin.getId()).setParameter("jour", new SimpleDateFormat("yyyy:MM:dd").parse(jour)).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 4);
    }
  }

   // een afspraak toevoegen
   // dag: dag van de afspraak
   // tijdvak: tijdvak van de afspraak
   // klant: klant voor wie de afspraak is gemaakt
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    try {
      Rv rv = new Rv(new SimpleDateFormat("yyyy:MM:dd").parse(jour), client, creneau);
      em.persist(rv);
      return rv;
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 5);
    }
  }

   // verwijdering van een afspraak
   // afspraak: de verwijderde afspraak
  public void supprimerRv(Rv rv) {
    try {
      em.remove(em.merge(rv));
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }

   // een bepaalde klant ophalen
  public Client getClientById(Long id) {
    try {
      return (Client) em.find(Client.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 7);
    }
  }

   // een bepaalde arts ophalen
  public Medecin getMedecinById(Long id) {
    try {
      return (Medecin) em.find(Medecin.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 8);
    }
  }

   // een bepaalde afspraak ophalen
  public Rv getRvById(Long id) {
    try {
      return (Rv) em.find(Rv.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 9);
    }
  }

   // een bepaald tijdvak ophalen
  public Creneau getCreneauById(Long id) {
    try {
      return (Creneau) em.find(Creneau.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 10);
    }
  }
}
  • regel 8: het object EntityManager dat de toegang tot de persistentiecontext beheert. Bij het instantiëren van de klasse wordt dit veld door de EJB-container geïnitialiseerd dankzij de annotatie @PersistenceContext op regel 7.
  • regel 15: query JPQL die alle rijen van de tabel [clients] retourneert in de vorm van een lijst met objecten [Client].
  • regel 22: soortgelijke query voor artsen
  • regel 32: een query JPQL die een join uitvoert tussen de tabellen [creneaux] en [medecins]. Deze wordt ingesteld op basis van de id van de arts.
  • regel 43: een query JPQL die een join uitvoert tussen de tabellen [rv], [creneaux] en [medecins] en twee parameters heeft: de id van de arts en de dag van de afspraak.
  • regels 55-57: aanmaken van een afspraak en deze vervolgens opslaan in de database.
  • regel 67: verwijdering van een afspraak uit de database.
  • regel 76: voert een SELECT-query uit op de database om een bepaalde klant te vinden
  • regel 85: idem voor een arts
  • regel 94: idem voor een afspraak
  • regel 103: idem voor een tijdslot
  • Alle bewerkingen met de persistentie-context em uit regel 9 kunnen een probleem met de database veroorzaken. Daarom zijn ze allemaal omgeven door een try/catch-blok. De eventuele uitzondering wordt ingekapseld in de "huis"-uitzondering RdvMedecinsException.

De EJB-module levert na compilatie een .jar-bestand op met de :

4.7. Implementatie van de EJB van de laag [dao] met NetBeans

Met NetBeans kun je de eerder gemaakte EJB eenvoudig op de GlassFish-server implementeren.

  • Controleer in de eigenschappen van het EJB-project de uitvoeringsopties [1].
  • In [2]: de naam van de server waarop de EJB wordt geïmplementeerd
  • in het tabblad [Services] [3] start u het op [4].
  • in [5], de Glassfish-server zodra deze is gestart. Deze heeft nog geen EJB-module.
  • Start de server MySQL en controleer of de database [dbrdvmedecins] online is. Hiervoor kunt u de NetBeans-verbinding gebruiken die in paragraaf 4.5 is aangemaakt.
  • In het tabblad [Projects] [6] wordt de EJB-module [7] geïmplementeerd: SGBD MySQL5 moet gestart zijn, zodat de door de EJB gebruikte bron JDBC "jdbc/dbrdvmedecins" toegankelijk is.
  • In [8] verschijnt de geïmplementeerde EJB in de boomstructuur van de GlassFish-server
  • In [9] wordt de geïmplementeerde EJB verwijderd
  • in [10] verschijnt de EJB niet meer in de boomstructuur van de GlassFish-server.

4.8. Implementatie van de EJB van de laag [dao] met GlassFish

Hier laten we zien hoe je een EJB vanuit het bijbehorende .jar-archief op de GlassFish-server implementeert.

  • Start de MySQL-server en controleer of de database [dbrdvmedecins] online is. Hiervoor kunt u de NetBeans-verbinding gebruiken die in paragraaf 4.5 is aangemaakt.

Laten we nog even de JPA-configuratie van de EJB-module die geïmplementeerd gaat worden, in herinnering brengen. Deze configuratie is vastgelegd in het bestand [persistence.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="dbrdvmedecins" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <properties>
       <!-- Dialect -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>

Regel 5 geeft aan dat de JPA-laag gebruikmaakt van een gegevensbron JTA, c.a.d, die wordt beheerd door de EJB3-container met de naam "jdbc/dbrdvmedecins".

In paragraaf 4.5 hebben we gezien hoe we deze bron JDBC vanuit NetBeans kunnen aanmaken. Hier laten we zien hoe dit rechtstreeks met GlassFish kan worden gedaan. We volgen hierbij een procedure die wordt beschreven in paragraaf 13.1.2, pagina 79 van [ref1].

We beginnen met het verwijderen van de resource, zodat we deze opnieuw kunnen aanmaken. Dit doen we vanuit NetBeans:

  • in [1], de resources JDBC van de GlassFish-server
  • in [2], de resource "jdbc/dbrdvmedecins" van onze EJB
  • in [3], de verbindingspool van deze resource JDBC
  • in [4], verwijderen we de verbindingspool. Dit heeft tot gevolg dat alle bronnen JDBC die hiervan gebruikmaken, worden verwijderd, dus ook de bron "jdbc/dbrdvmedecins".
  • In [5] en [6] zijn de resource JDBC en de verbindingspool verwijderd.

Nu gebruiken we de beheerconsole van de Glassfish-server om de resource JDBC aan te maken en de EJB te implementeren.

  • in het tabblad [services] [1] van NetBeans, start de GlassFish-server [2] en ga vervolgens naar [3] de beheerconsole
  • in [4], log in als beheerder (wachtwoord: adminadmin als u dit tijdens de installatie of daarna niet hebt gewijzigd).
  • in [5], selecteer de tak [Connection Pools] van de GlassFish-bronnen
  • in [6], maak een nieuwe verbindingspool aan. Ter herinnering: een verbindingspool is een techniek om het aantal keer dat verbindingen met een SGBD worden geopend en gesloten te beperken. Bij het opstarten van de server N worden er, een aantal dat per configuratie wordt bepaald, verbindingen geopend met de SGBD. Deze geopende verbindingen worden vervolgens ter beschikking gesteld aan de EJB’s die erom vragen om een bewerking uit te voeren met de SGBD. Zodra deze is voltooid, geeft de EJB de verbinding terug aan de pool. De verbinding wordt nooit gesloten. Deze wordt gedeeld door de verschillende threads die toegang hebben tot de SGBD
  • in [7], geef de pool een naam
  • in [8]; de klasse die de gegevensbron modelleert is de klasse [javax.sql.DataSource]
  • in [9], de SGBD die de gegevensbron bevat, is hier MySQl.
  • in [10], ga dan naar de volgende stap
  • In [11] zorgt het attribuut "Connection Validation Required" ervoor dat de pool, voordat een verbinding wordt toegewezen, controleert of deze operationeel is. Als dat niet het geval is, maakt de pool een nieuwe verbinding aan. Hierdoor kan een applicatie blijven functioneren na een tijdelijke onderbreking met de SGBD. Tijdens de onderbreking is er geen enkele verbinding bruikbaar en worden er uitzonderingen doorgegeven aan de client. Zodra de onderbreking voorbij is, krijgen de clients die nog steeds verbindingen aanvragen deze weer toegewezen: dankzij het attribuut "Connection Validation Required" worden alle verbindingen in de pool opnieuw aangemaakt. Zonder dit attribuut zou de pool weliswaar vaststellen dat de oorspronkelijke verbindingen zijn verbroken, maar zou hij niet proberen nieuwe aan te maken.
  • In [12] wordt het isolatieniveau „Read Committed“ voor transacties gevraagd. Dit niveau zorgt ervoor dat een transactie T2 geen gegevens kan lezen die zijn gewijzigd door een transactie T1, zolang deze laatste niet volledig is voltooid.
  • In [13] wordt gevraagd dat alle transacties het isolatieniveau gebruiken dat is opgegeven in [12]
  • in [14] en [15] moet u de URL specificeren van de BD waarvan de pool de verbindingen beheert
  • in [16], de gebruiker is dan root
  • in [17], voeg een eigenschap toe
  • in [18]: voeg de eigenschap "Password" toe met de waarde () in [19]. Hoewel dit niet te zien is op de schermafbeelding van [19], mag je geen lege tekenreeks invoeren, maar wel () (open haakje, sluitend haakje) om een leeg wachtwoord aan te duiden. Als de gebruiker root van uw SGBD MySQL een niet-leeg wachtwoord heeft, voer dan dit wachtwoord in.
  • in [20], voltooi dan de wizard voor het aanmaken van de verbindingspool voor de database MySQL [dbrdvmedecins].
  • in [21] is de pool aangemaakt. Klik op de link.
  • in [22] kunt u met de knop [Ping] een verbinding tot stand brengen met de database [dbrdvmedecins]
  • in [23]: als alles goed gaat, verschijnt er een bericht dat de verbinding tot stand is gebracht

Zodra de verbindingspool is aangemaakt, kun je een JDBC-bron aanmaken:

  • in [1]. Selecteer de tak [JDBC Resources] in de objectboom van de server
  • in [2] maakt u een nieuwe bron aan: JDBC
  • in [3], geef je de bron JDBC een naam. Deze moet overeenkomen met de naam die in het bestand [persistence.xml] wordt gebruikt:
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
  • in [4] wordt de verbindingspool gespecificeerd die de nieuwe resource JDBC moet gebruiken: de pool die zojuist is aangemaakt
  • In [5] voltooit u de aanmaakwizard
  • in [6] de nieuwe resource JDBC

Nu de resource JDBC is aangemaakt, kunnen we het JAR-archief van de EJB implementeren:

  • naar [1], selecteer de branch [Enterprise Applications]
  • naar [2], met de knop [Deploy], geef aan dat u een nieuwe applicatie wilt implementeren
  • in [3], geef aan dat de applicatie een EJB-module is
  • in [4] selecteert u de EJB-jar [serveur-ejb-dao-jpa-hibernate.jar] die u voor TP hebt ontvangen.
  • in [5] kunt u de naam van de EJB-module desgewenst wijzigen
  • in [6]: voltooi de implementatiewizard voor de EJB-module
  • In [7] is de EJB-module geïmplementeerd. Deze kan nu worden gebruikt.

4.9. Testen van de EJB van de laag [dao]

Nu de EJB van de laag [dao] van onze applicatie is geïmplementeerd, kunnen we deze testen. We doen dit met behulp van de volgende Java-client:

De klasse [MainTestsDaoRemote] [1] is een testklasse JUnit 4. De bibliotheken in [2] bestaan enerzijds uit:

  • de JAR-bestanden van de EJB van de laag [dao] [3] (zie paragraaf 4.6.4).
  • de GlassFish-bibliotheken [4] die nodig zijn voor de externe clients van de EJB's.

De testklasse is als volgt:

package dao;
...
public class MainTestsDaoRemote {

   // laag [dao] getest
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
     // omgeving initialiseren JNDI
    InitialContext initialContext = new InitialContext();
     // instantiëren van de DAO-laag
    dao = (IDaoRemote) initialContext.lookup("rdvmedecins.dao");
  }

  @Test
  public void test1() {
     // testgegevens
    String jour = "2006:08:23";
     // weergave klanten
    List<Client> clients = null;
    try {
      clients = dao.getAllClients();
      display("Liste des clients :", clients);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // weergave van artsen
    List<Medecin> medecins = null;
    try {
      medecins = dao.getAllMedecins();
      display("Liste des médecins :", medecins);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // weergave van de tijdvakken van een arts
    Medecin medecin = medecins.get(0);
    List<Creneau> creneaux = null;
    try {
      creneaux = dao.getAllCreneaux(medecin);
      display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // lijst met afspraken van een arts op een bepaalde dag
    try {
      display(String.format("Liste des créneaux du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // een RV toevoegen
    Rv rv = null;
    Creneau creneau = creneaux.get(2);
    Client client = clients.get(0);
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // een RV toevoegen in hetzelfde tijdvak op dezelfde dag
     // moet een uitzondering veroorzaken
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // een RV verwijderen
    System.out.println("Suppression du Rv ajouté");
    try {
      dao.supprimerRv(rv);
      System.out.println("Rv supprimé");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
  }

   // hulpprogramma - toont de elementen van een verzameling
  private static void display(String message, List elements) {
    System.out.println(message);
    for (Object element : elements) {
      System.out.println(element);
    }
  }
}
  • regel 13: let op de instantiëring van de proxy van de externe EJB. We gebruiken de naam JNDI "rdvmedecins.dao".
  • De testmethoden maken gebruik van de methoden die door de EJB worden blootgesteld (zie paragraaf 4.6.4).

Als alles goed gaat, moeten de tests slagen:

 

Nu de EJB van de laag [dao] operationeel is, kunnen we overgaan tot de openbare beschikbaarstelling ervan via een webservice.

4.10. De webservice van de laag [dao]

Voor een korte inleiding tot het begrip webservice verwijzen we naar paragraaf 14, pagina 111 van [ref1].

Laten we terugkeren naar de serverarchitectuur van onze client/server-toepassing:

Hierboven richten we ons op de webservice van de laag [dao]. Deze service heeft als enige taak de interface van de EJB van de laag [dao] beschikbaar te maken voor clients op verschillende platforms die in staat zijn om met een webservice te communiceren.

Ter herinnering: er zijn twee manieren om een webservice te implementeren:

  • via een klasse met de annotatie @WebService die in een webcontainer wordt uitgevoerd
  • via een EJB met de annotatie @WebService die wordt uitgevoerd in een EJB-container

We gebruiken hier de eerste oplossing. In NetBeans moeten we een bedrijfsproject opzetten met twee modules:

  • de EJB-module die in de EJB-container wordt uitgevoerd: de EJB van de [dao]-laag.
  • de webmodule die in de webcontainer zal draaien: de webservice die we momenteel aan het bouwen zijn.

We gaan dit bedrijfsproject op twee manieren opzetten.

4.10.1. NetBeans-project – Versie 1

We bouwen eerst een NetBeans-project van het type „Web Application“:

  • in [1] maken we een nieuw project aan in de categorie "Java Web" [2] van het type "Web Application" [3].
  • in [4], geef je het project een naam en in [5] geef je de map aan waarin het moet worden gegenereerd
  • in [6] stel je de applicatieserver in die de webapplicatie gaat uitvoeren
  • in [7] wordt de context van de applicatie ingesteld
  • in [8] wordt de projectconfiguratie gevalideerd.
  • in [9] wordt het gegenereerde project weergegeven. De webservice die we bouwen, zal gebruikmaken van de EJB van het vorige project [10]. Daarom moet er worden verwezen naar de .jar van de EJB-module [10].
  • In [11] voegen we een NetBeans-project toe aan de bibliotheken van het webproject [12]
  • in [13] selecteert men de map van de EJB-module in het bestandssysteem en bevestigt men.
  • in [14] is de EJB-module toegevoegd aan de bibliotheken van het webproject.

In [15] implementeren we de webservice met de volgende klasse [WsDaoJpa]:

package rdvmedecins.ws;
...
@WebService()
public class WsDaoJpa implements IDao {

  @EJB
  private IDaoLocal dao;

   // lijst met klanten
  @WebMethod
  public List<Client> getAllClients() {
    return dao.getAllClients();
  }

   // lijst met artsen
  @WebMethod
  public List<Medecin> getAllMedecins() {
    return dao.getAllMedecins();
  }

   // lijst met beschikbare tijdvakken van een bepaalde arts
   // arts: de arts
  @WebMethod
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    return dao.getAllCreneaux(medecin);
  }

   // lijst met afspraken van een bepaalde arts op een bepaalde dag
   // arts: de arts
   // dag: de dag
  @WebMethod
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    return dao.getRvMedecinJour(medecin, jour);
  }

   // een afspraak toevoegen
   // dag: dag van de afspraak
   // tijdvak: tijdvak van de afspraak
   // klant: klant voor wie de afspraak is gemaakt
  @WebMethod
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    return dao.ajouterRv(jour, creneau, client);
  }

   // verwijdering van een afspraak
   // afspraak: de verwijderde afspraak
  @WebMethod
  public void supprimerRv(Rv rv) {
    dao.supprimerRv(rv);
  }

   // een bepaalde klant ophalen
  @WebMethod
  public Client getClientById(Long id) {
    return dao.getClientById(id);
  }

   // een bepaalde arts ophalen
  @WebMethod
  public Medecin getMedecinById(Long id) {
    return dao.getMedecinById(id);
  }

   // een bepaalde afspraak ophalen
  @WebMethod
  public Rv getRvById(Long id) {
    return dao.getRvById(id);
  }

   // een bepaald tijdvak ophalen
  @WebMethod
  public Creneau getCreneauById(Long id) {
    return dao.getCreneauById(id);
  }
}
  • In regel 4 implementeert de klasse [WsdaoJpa] de interface [IDao]. Ter herinnering: deze interface is gedefinieerd in het EJB-archief van de laag [dao] in de volgende vorm:
package rdvmedecins.dao;
...
public interface IDao {

   // lijst met klanten
  public List<Client> getAllClients();
   // lijst met artsen
  public List<Medecin> getAllMedecins();
   // lijst met tijdvakken van een arts
  public List<Creneau> getAllCreneaux(Medecin medecin);
   // lijst met afspraken van een arts op een bepaalde dag
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // een klant zoeken op basis van zijn ID
  public Client getClientById(Long id);
   // een klant zoeken op basis van zijn ID
  public Medecin getMedecinById(Long id);
   // een afspraak zoeken op basis van de id
  public Rv getRvById(Long id);
   // een tijdslot zoeken op basis van de id
  public Creneau getCreneauById(Long id);
   // een RV toevoegen
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // een RV verwijderen
  public void supprimerRv(Rv rv);
}
  • regel 3: de annotatie @WebService maakt van de klasse [WsDaoJpa] een webservice.
  • regels 6-7: de EJB-referentie van de laag [dao] wordt door de applicatieserver in het veld van regel 7 geïnjecteerd. Ter herinnering: het is altijd de lokale implementatie (hier IDaoLocal) die op deze manier wordt geïnjecteerd. Deze injectie is mogelijk omdat de webservice in dezelfde JVM draait als de EJB.
  • Alle methoden van de webservice zijn gemarkeerd met de annotatie @WebMethod om ze zichtbaar te maken voor externe clients. Een methode die niet is gemarkeerd met de annotatie @WebMethod zou intern zijn voor de webservice en niet zichtbaar voor externe clients. Elke methode M van de webservice roept gewoon de overeenkomstige methode M aan van de EJB die op regel 7 is geïnjecteerd.

Het aanmaken van deze webservice wordt weerspiegeld door een nieuwe tak in het NetBeans-project:

In [1] zien we de webservice WsDaoJpa en in [2] de methoden die deze aan externe clients blootstelt.

Laten we nog eens de architectuur van de webdienst in ontwikkeling bekijken:

De componenten van de webservice die we gaan implementeren zijn:

  • [1]: de webmodule die we zojuist hebben gebouwd
  • [2]: de EJB-module die we in een eerdere stap hebben gebouwd en waarvan de webservice afhankelijk is

Om ze samen te implementeren, moeten de twee modules worden samengebracht in een zogenaamd "bedrijfs"-project in NetBeans:

In [1] maken we een nieuw bedrijfsproject aan: [2, 3].

  • In [4,5] geef je het project een naam en stel je de map in waarin het wordt aangemaakt
  • in [6] kiest men de applicatieserver waarop de bedrijfsapplicatie zal worden geïmplementeerd
  • in [7]: een bedrijfsproject kan drie componenten hebben: webapplicatie, EJB-module, clientapplicatie. Hier wordt het project zonder componenten aangemaakt. Deze worden later toegevoegd.
  • in [8], de zojuist aangemaakte bedrijfsapplicatie.
  • in [9], klik met de rechtermuisknop op [Java EE Modules] en voeg een nieuwe module toe
  • in [10] worden alleen de NetBeans-modules weergegeven die momenteel geopend zijn in IDE. Hier selecteren we de webmodule [serveur-webservice-1-ejb-dao-jpa-hibernate] en de EJB-module [serveur-ejb-dao-jpa-hibernate] die we hebben gebouwd.
  • In [11] zijn de twee modules toegevoegd aan het bedrijfsproject.

Nu moeten we deze bedrijfsapplicatie nog op de GlassFish-server implementeren. Vervolgens moet SGBD MySQL worden gestart, zodat de gegevensbron JDBC "jdbc/dbrdvmedecins" die door de EJB-module wordt gebruikt, toegankelijk is.

  • in [1] wordt de Glassfish-server gestart
  • als de EJB-module [serveur-ejb-dao-jpa-hibernate] is geïmplementeerd, wordt deze ontkoppeld [2]
  • naar [3], wordt de bedrijfsapplicatie geïmplementeerd
  • in [4] is deze geïmplementeerd. We zien dat deze de twee modules bevat: Web en EJB.

4.10.2. NetBeans-project – versie 2

We laten nu zien hoe de webservice geïmplementeerd kan worden wanneer men niet over de broncode van de EJB-module beschikt, maar alleen over het bijbehorende .jar-archief.

Het nieuwe NetBeans-project voor de webservice ziet er als volgt uit:

De opvallende elementen van het project zijn de volgende:

  • [1]: de webservice wordt geïmplementeerd door een NetBeans-project van het type [Web Application].
  • [2]: de webservice wordt geïmplementeerd door de klasse [WsDaoJpa] die we al hebben besproken
  • [3]: het EJB-archief van de laag [dao], waardoor de klasse [WsDaoJpa] toegang heeft tot de definities van de verschillende klassen, interfaces en entiteiten van de lagen [dao] en [jpa].

Vervolgens bouwen we het bedrijfsproject dat nodig is voor de implementatie van de webservice:

  • [1], we maken een bedrijfsapplicatie [ea-rdvmedecins], in eerste instantie zonder modules.
  • In [2] voegen we de eerdere webmodule [serveur-webservice-ejb-dao-jpa-hibernate] toe
  • in [3], het resultaat.

In deze vorm kan de bedrijfsapplicatie [ea-rdvmedecins] niet vanuit NetBeans op de GlassFish-server worden geïmplementeerd. Er treedt een fout op. Het ear-archief van de applicatie [ea-rdvmedecins] moet dan handmatig worden geïmplementeerd:

  • Het archief [ea-rdvmedecins.ear] is te vinden in de map [dist] [2] op het tabblad [Files] van NetBeans.
  • In dit archief [3] bevinden zich de twee onderdelen van de bedrijfsapplicatie:
  • het EJB-archief [serveur-ejb-dao-jpa-hibernate]. Dit archief is aanwezig omdat het deel uitmaakte van de bibliotheken waarnaar de webservice verwijst.
  • het archief van de webservice [serveur-webservice- ejb-dao-jpa-hibernate].
  • Het archief [ea-rdvmedecins.ear] is samengesteld uit een eenvoudig Build en [4] van de bedrijfsapplicatie.
  • In [5] mislukt de implementatiebewerking.

Om het archief [ea-rdvmedecins.ear] van de bedrijfsapplicatie te implementeren, gaan we te werk zoals beschreven bij de implementatie van het EJB-archief [serveur-ejb-dao-jpa-hibernate.jar] in paragraaf 4.2. We gebruiken opnieuw de webclient voor het beheer van de GlassFish-server. We herhalen de reeds beschreven stappen niet.

Allereerst beginnen we met het „uitpakken“ van de bedrijfsapplicatie die in paragraaf 4.10.1 is geïmplementeerd:

  • [1]: selecteer de branch [Enterprise Applications] van de GlassFish-server
  • selecteer in [2] de bedrijfsapplicatie die moet worden verwijderd en verwijder deze vervolgens in [3]
  • in [4] is de bedrijfsapplicatie verwijderd
  • in [1], kies de branch [Enterprise Applications] van de Glassfish-server
  • in [2], implementeer een nieuwe bedrijfsapplicatie
  • in [3], selecteer het type [Enterprise Application]
  • in [4], wijs het .ear-bestand van het NetBeans-project aan [ea-rdvmedecins]
  • in [5], implementeer dit archief
  • in [6], de applicatie is geïmplementeerd
  • in [7], de webservice [WsDaoJpa] verschijnt in de branch [Web Services] van de GlassFish-server. We selecteren deze.
  • In [8] is diverse informatie over de webservice beschikbaar. Het meest interessant voor een klant is de informatie [9]: de URI van de webservice.
  • In [10] kun je de webservice testen
  • In [11] is de -URI van de webservice waaraan de parameter ?test is toegevoegd. Deze URI toont een testpagina. Alle methoden (@WebMethod) die door de webservice worden aangeboden, worden weergegeven en kunnen worden getest. Hier testen we de methode [13], die de lijst met klanten opvraagt.
  • In [14] tonen we slechts een gedeeltelijk overzicht van de antwoordpagina. Maar we kunnen zien dat de methode getAllClients de lijst met klanten inderdaad heeft teruggestuurd. De schermafbeelding laat zien dat de methode haar antwoord in het formaat XML verstuurt.

Een webservice wordt volledig beschreven door een bestand met de naam XML, ook wel het WSDL-bestand genoemd:

  • in [1] in de webbeheertool van de Glassfish-server, selecteer de webservice [WsDaoJpa]
  • naar [2], volg de link [View WSDL]
  • naar [3]: de URI van het bestand WSDL. Dit is belangrijke informatie om te weten. Deze is nodig om de clients van deze webservice te configureren.
  • in [4], de beschrijving XML van de webservice. We zullen geen toelichting geven op deze complexe inhoud.

4.10.3. Tests JUnit van de webservice

We maken een NetBeans-project aan om de tests die al met een EJB-client zijn uitgevoerd, nu te „herhalen” met een client voor de onlangs geïmplementeerde webservice. We volgen hierbij een aanpak die vergelijkbaar is met die beschreven in paragraaf 14.2.1, pagina 115 van [ref1].

  • in [1], een klassiek Java-project
  • in [2], de testklasse
  • in [3] gebruikt de client het EJB-archief om toegang te krijgen tot de definities van de interface van de laag [dao] en de JPA-entiteiten. Ter herinnering: dit archief bevindt zich in de submap [dist] van de EJB-modulemap.

Om toegang te krijgen tot de externe webservice, moeten er proxyklassen worden gegenereerd:

In het bovenstaande schema communiceert de laag [2] [C=Client] met de laag [1] [S=Serveur]. Om te communiceren met de laag [S] moet de client [C] een netwerkverbinding tot stand brengen met de laag [S] en met deze laag communiceren volgens een specifiek protocol. De netwerkverbindingen zijn van het type TCP en het transportprotocol is HTTP. De laag [S], die de webservice vertegenwoordigt, wordt geïmplementeerd door een Java-servlet die wordt uitgevoerd door de Glassfish-server. We hebben deze servlet niet zelf geschreven. De generatie ervan wordt door Glassfish geautomatiseerd op basis van de annotaties @Webservice en @WebMethod van de klasse [WsDaoJpa] die we hebben geschreven. Op dezelfde manier gaan we de generatie van de [C]-laag van de client automatiseren. De laag [C] wordt soms een proxylaag van de externe webservice genoemd, waarbij de term proxy verwijst naar een tussenelement in een softwareketen. Hier fungeert de C-proxy als tussenpersoon tussen de client die we gaan schrijven en de webservice die we hebben geïmplementeerd.

Met NetBeans 6.5 kan de C-proxy als volgt worden gegenereerd (hiervoor moet de webservice actief zijn op de GlassFish-server):

  • in [1], voeg een nieuw element toe aan het Java-project
  • in [2], selecteer de branch [Web services]
  • in [3], selecteer [Web Service Client]
  • in [4], de URI van het bestand WSDL van de webservice opgeven. Deze URI is beschreven in paragraaf 4.10.2.
  • in [5], laat de standaardwaarde [JAX-WS] staan. De andere mogelijke waarde is [JAX-RPC]
  • Nadat de wizard voor het aanmaken van de webservice-proxy was bevestigd, werd het NetBeans-project uitgebreid met een tak [Web Service References] [6]. Deze tak toont de methoden die door de externe webservice worden aangeboden.
  • Op het tabblad [Files] [7] is Java-broncode toegevoegd [8]. Deze komt overeen met de gegenereerde C-proxy.
  • In [9] staat de code van een van de klassen. Daaruit blijkt [10] dat ze in een pakket [rdvmedecins.ws] zijn geplaatst. We zullen geen commentaar geven op de code van deze klassen, die opnieuw vrij complex is.

Voor de Java-client die we aan het bouwen zijn, fungeert de gegenereerde C-proxy als tussenpersoon. Om toegang te krijgen tot de methode M van de externe webservice, roept de Java-client de methode M van de C-proxy aan. De Java-client roept dus lokale methoden aan (die in dezelfde JVM worden uitgevoerd) en op een voor hem transparante manier worden deze lokale aanroepen omgezet in externe aanroepen.

Nu moeten we nog leren hoe we de methoden M van de C-proxy kunnen aanroepen. Laten we teruggaan naar onze testklasse JUnit:

In [1] is de testklasse [MainTestsDaoRemote] dezelfde die al werd gebruikt bij het testen van de EJB van de laag [dao]:

package dao;
...
public class MainTestsDaoRemote {

   // laag [dao] getest
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
  }

  @Test
  public void test1() {
...
  }
}
  • regel [13]: de test test1 blijft ongewijzigd.
  • regel [9]: de inhoud van de methode [init] is verwijderd.

Op dit moment bevat het project fouten, omdat de testmethode [test1] gebruikmaakt van de entiteiten [Client], [Medecin], [Creneau], [Rv], die zich niet meer in dezelfde pakketten bevinden als voorheen. Ze bevinden zich nu in het pakket van de gegenereerde C-proxy. De betreffende instructies import worden verwijderd en vervolgens opnieuw gegenereerd met de bewerking ‘Fix Imports’.

Laten we teruggaan naar de code van de testklasse [MainTestsDaoRemote]:

package dao;
...

public class MainTestsDaoRemote {

   // laag [dao] getest
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
}

De methode [init] op regel 10 moet de referentie van de laag [dao] op regel 7 initialiseren. We moeten weten hoe we de gegenereerde C-proxy in onze code kunnen gebruiken. NetBeans helpt ons hierbij.

  • Selecteer in [1] de methode [getAllClients] van de webservice met de muis en sleep deze methode vervolgens naar de methode [init] van de testklasse.

Het resultaat is [2]. Dit codekader laat zien hoe de gegenereerde C-proxy moet worden gebruikt:

1
2
3
4
5
6
7
8
9
    try { // Webservice-bewerking aanroepen
      rdvmedecins.ws.WsDaoJpaService service = new rdvmedecins.ws.WsDaoJpaService();
      rdvmedecins.ws.WsDaoJpa port = service.getWsDaoJpaPort();
       // TODO verwerk het resultaat hier
      java.util.List<rdvmedecins.ws.Client> result = port.getAllClients();
      System.out.println("Result = "+result);
    } catch (Exception ex) {
       // TODO aangepaste uitzonderingen hier afhandelen
}
  • De regel [5] laat zien dat de methode [getAllClients] een methode is van het object van het type [WsDaoJpa], gedefinieerd op regel 3. Het type [WsDaoJpa] is een interface met dezelfde methoden als die welke door de externe webservice worden blootgesteld.
  • In regel [3] wordt het object [WsDaoJpa port] verkregen uit een ander object van het type [WsDaoJpaService], gedefinieerd in regel 2. Het type [WsDaoJpaService] vertegenwoordigt de lokaal gegenereerde C-proxy.
  • De toegang tot de externe webservice kan mislukken, daarom is de volledige code omgeven door een try/catch-blok.
  • De objecten van de C-proxy bevinden zich in het pakket [rdvmedecins.ws]

Als je deze code eenmaal begrijpt, zie je dat de lokale referentie van de externe webservice kan worden verkregen met de volgende code:

WsDaoJpa dao=new WsDaoJpaService().getWsDaoJpaPort();

De code van de testklasse JUnit wordt dan als volgt:

package dao;

import rdvmedecins.ws.Client;
import rdvmedecins.ws.Creneau;
import rdvmedecins.ws.Medecin;
import rdvmedecins.ws.Rv;
import rdvmedecins.ws.WsDaoJpa;
import rdvmedecins.ws.WsDaoJpaService;
...

public class MainTestsDaoRemote {

   // laag [dao] getest
  private static WsDaoJpa dao;

  @BeforeClass
  public static void init(){
    dao=new WsDaoJpaService().getWsDaoJpaPort();
  }

  @Test
  public void test1() {
...
  }

   // hulpprogramma - toont de elementen van een verzameling
  private static void display(String message, List elements) {
 ...
  }
}

We zijn nu klaar om te testen:

In [1] wordt de test JUnit uitgevoerd. In [2] is deze geslaagd. Als we de uitvoer op de NetBeans-console bekijken, zien we regels zoals de volgende:

Liste des clients :
rdvmedecins.ws.Client@1982fc1
rdvmedecins.ws.Client@676437
rdvmedecins.ws.Client@1e4853f
rdvmedecins.ws.Client@1e808ca

Aan de serverzijde heeft de entiteit [Client] een methode toString die de verschillende velden van een object van het type [Client] weergeeft. Bij het automatisch genereren van de C-proxy worden de entiteiten in de C-proxy aangemaakt, maar dan alleen met de privévelden en de bijbehorende get-/set-methoden. De methode toString is dus niet gegenereerd in de entiteit [Client] van de C-proxy. Dit verklaart de eerder getoonde weergave. Dit doet niets af aan de test JUnit: deze is geslaagd. We gaan er nu vanuit dat we een operationele webservice hebben.