Développement d'applications Web & Distribuées avec JavaEE

Utilisation des technologies de la plateforme JavaEE : Servlets, JSP, JTSL, EJB, JPA

Introduction

Prérequis

Installation de Jetbrains IntelliJ IDEA et Jetbrains Datagrip

Licence

IntelliJ et Datagrip sont des outils professionnels sous licence. Heureusement, Jetbrains permet aux étudiants de bénéficier de licences gratuites pour tous ses outils. Elles sont accessible via le Github Student Pack. Une fois le student pack en main, rdv ici pour créer un compte Jetbrains en utilisant son compte Github.

Avec JetBrain Toolbox

Jetbrains Toolbox est un petit utilitaire très pratique qui permet d'installer et mettre à jours les logiciels JetBrains en un clic.

Installer Jetbrains Toolbox

Téléchargez et installez Jetbrains Toolbox

Installer IntelliJ

Exécutez JetBrains Toolbox puis faites un clic droit sur son icône dans la barre d'état système de Windows. Trouvez ensuite IntelliJ et Datagrip dans la liste des applications proposée. Cliquez sur "Install" et attendez la fin du téléchargement.

Sans JetBrains ToolBox

Téléchargez IntelliJ via ce lien et datagrip via ce lien, puis exécutez l'installateur. Suivez ensuite les instructions d'installation.

Installer une base de donnée MySQL

Laragon est un outil qui package plusieurs outils pour le développement web. Nous allons l'utiliser la base de donnée MySQL. Téléchargez Laragon via ce lien,puis exécutez l'installateur. Suivez ensuite les instructions d'installation.

Configurer Laragon

Lancez Laragon puis faites clic droit sur le fond > MySQL > Change root password. Mettez un mot de passe et conservez le bien il servira à se connecter à la base de donnée. Lancez ensuite le serveur en faisant clic droit sur le fond > MySQL > Start MySQL

Créer une base de donnée

Vous pouvez créer une base de donnée pour chacun de vos projet en faisant : clic droit sur le fond > MySQL > Create Database, puis donnez lui un nom.

Installer une base de donnée Oracle

Exec DBMS_XDB.SETHTTPPORT(3010);
CREATE USER nom_utilisateur IDENTIFIED BY mot_de_passe;
GRANT DBA TO nom_utilisteur;

Se connecter à une base de donnée avec Datagrip

Lancer Datagrip puis cliquer sur le bouton "+" :

2020-09-12-17_17_24-.png

Et choisissez "Datasource > MariaDB" :

2020-09-12-17_18_48-.png

Au bas du formulaire cliquez sur "Download missing driver file". Remplissez ensuite le formulaire comme suit :

Faites ensuite "Test Connection" afin de voir si tout est bien configuré, puis fait "Apply". Votre base de donnée est apparue dans la liste des bases de donnée à gauche de l'interface. Pour ouvrir une session SQL faites Clic droit sur le nom de votre base > New > Query Console :

2020-09-12-17_25_20-.png

Vous avez donc une console dans laquelle vous pouvez écrire des scripts SQL. CTRL + ENTER pour exécuter votre script.

Conteneur de Servlets Apache Tomcat

Apache Tomcat est un conteneur de servlets. Il sert à exécuter des composants de serveur HTTP écrits en Java, les servlet, sur lesquels nous reviendront plus tard.

Créer un projet JakartaEE avec IntelliJ

Créer le projet

Créez un nouveau projet en utilisant le template "Java Entreprise" :

Screenshot 2022-02-21 094421.png

Explications :

Ajouter un serveur d'application

Pour ajouter un nouveau serveur d'application sur votre IntelliJ, cliquez sur "New" et séléctionner le type de server d'application que vous voulez utiliser. Dans ce tutoriel, nous allons utiliser Tomcat dans un premier temps, puis Wildfly dans la partie avancée.

Capture d’écran 2021-05-23 102509.png

Ensuite renseignez simplement le répertoire de votre serveur d'application :

Capture d’écran 2021-05-23 102536.png

Faites "suivant".

Dépendances JakartaEE

Sur l'écran suivante, dans les dépendances, nous devons choisir quelles spécifications de la plateforme JakartaEE notre application va utiliser. Il faut aussi se référer pour la version à prendre, à la version de notre serveur d'application :

Screenshot 2022-02-21 094712.png

Par exemple, pour les versions 10.0.x (ce qui est mon cas à l'heure où j'écris ces lignes), il faut prendre la spécification de servlet en version 5.0.

Pour accèder à la dépendance de la Servlet API 5.0, vous devez positionner la version de JakartaEE à 9 :

Capture d’écran 2021-05-23 102321.png

Ces dépendances ne vont télécharger ques les interfaces de la spécification dans votre projet (pour pouvoir les utiliser dans le code), les implémentations seront fournies par votre serveur d'application.

Tester le projet

IntelliJ vous a généré un projet Maven avec les dépendances JakartaEE demandées ainsi, qu'une configuration de déploiement pour votre serveur d'application. Vous pouvez simplement lancer votre projet en cliquant sur le boutun "Run" et la page racine de votre application devrait s'ouvrir :

Capture d’écran 2021-05-23 102719.png

Base des applications JavaEE

Développer des applications Web simple avce Servlets, JSP et JDBC

Base des applications JavaEE

Java EE

Qu'est ce que JavaEE ?

Java EE ou J2EE ou Jakarta EE est un ensemble de spécifications de différentes API orientée pour le développement d'applications professionnelles et d'entreprise. Chacune de ces spécifications d'API répond à un besoin courant de ce type d'application, comme par exemple, accèder à une base de donnée, répondre à des requêtes HTTP, etc ...

Les Serveurs d'applications

Les serveurs d'application sont les environnement d'exécutions pour les applications JavaEE. Ils implémentes certaines ou toutes les spécifications d'API. Quelques exemples :

Ainsi, les applications JavaEE ne sont pas exécutées comme les applications JavaSE via une méthode main. Elles doivent être packagées puis déployées sur un serveur d'application. Heureusement, notre IDE rend cette tâche triviale.

Base des applications JavaEE

Servlet API

Les servlets

La Servlet API est une brique fondamentale de la spécification JavaEE. Elle permet de développer des composants gérant des requêtes HTTP. L'instanciation des servlets n'est pas gérées par le programmeur, mais par ce que l'ont appelle le conteneur de servlets. Il s'agit d'une partie du serveur d'application qui orchestre le cycle de vie des servlets ainsi que les threads nécessaires à la gestion des requêtes.

Pour créer une servlet, il suffit de créer une classe qui étend la classe HttpServlet et de lui affecter l'annotation @WebServlet avec en paramètre la route qui est être gérée par la servlet.

@WebServlet("/helloworld")
public class HelloWorldServlet extends HttpServlet {
    
}

Dans une servlet vous pouvez redéfinir certaines méthodes, notamment doGet et doPost. Ces méthodes sont appelées par le conteneur lorsqu'une requête HTTP est faite sur la route de la servlet. doGet gère les requête avec la méthode HTTP GET et doPost gère les requêtes avec la méthode HTTP POST.

@WebServlet("/helloworld")
public class HelloWorldServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

Les paramètres de cetes méthodes, représentent respectivement la requête HTTP et la réponse HTTP.

Objet requête

Paramètres

L'objet requête permet de récupérer des paramètres soit passés dans l'URL de la requête dans le cas d'une requête HTTP GET, ou paramètres d'un formulaire (passés dans le Body de la requête au format FormData) :

String monParam = request.getParameter("nomDuParam");

Session

L'objet requête permet également de récupérer la session du client. Dans la session, il est possible de stocker des variables et de les récupérer. La session est propre à un client.

// Récupérer la session
HttpSession session = request.getSession();	

// Ecrire une variable dans la session
session.setAttribute("profil", profil);

// Récupérer une variable de session
ProfilUtilisateur profil = (ProfilUtilisateur) session.getAttribute("profil");

Objet réponse

L'objet réponse permet d'écrire du texte dans le Body de la réponse HTTP :

response.getOutputStream().println("Hello world");

On peut également faire une redirection vers une autre route de l'application :

response.sendRedirect("/helloworld");

Première Servlet

Vous pouvez désormais créer une servlet "Hello world" :

@WebServlet("/helloworld")
public class HelloWorldServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getOutputStream().println("Hello world");
    }
}

Ensuite lancez le projet, une page web dans votre navigateur devrait apparaître. Ajoutez /helloworld à la fin de l'URL et vous devriez voir le message hello world :

Capture d’écran 2020-11-25 134614.png

Récupération d'un paramètre GET

Pour récupérer un paramètre GET, on utilise la méthode getParameter de la requête :

@WebServlet("/helloworld")
public class HelloWorldServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name"); 
        response.getOutputStream().println("Hello " + name + "!");
    }
}

Relancez votre projet et ajoutez un paramètre GET dans l'URL !

Soumission d'un formulaire

Nous allons maintenant voir comment gérer une soumission de formulaire avec une servlet. Dans le dossier webapp, vous devriez trouver une page index.jsp. Considérer là comme une simple page HTML, nous verrons les JSP dans la partie d'après. Si elle n'est pas présente créez simplement une page index.html. Ecrivez maintenant un formulaire POST simple :

<form method="post" action="/helloworld">
  
  <input type="text" name="monParam"/>
  <button type="submit">Envoyer</button>
  
</form>

Dans ce formulaire, l'action est mappée sur l'URL de notre servlet. Il faut donc que notre servlet implémente la méthode doPost pour gérer les requêtes POST. On peut ensuite récupérer le paramètre POST de la même façon que les paramètres GET :

@WebServlet("/helloworld")
public class HelloWorldServlet extends HttpServlet {

  	...
  
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("monParam"); 
        response.getOutputStream().println("Hello " + name + "!");
    }
}
Base des applications JavaEE

JSP/JSTL

Les Java Server Pages

Ecrire une interface HTML dans une string Java n'est pas très pratique. C'est pourquoi il est possible d'utiliser pour ce faire les JSP. Les Java Server Pages sont des templates de vues qui sont rendus coté serveur. Lorsque vous renvoyez une JSP depuis une Servlet, elle va être interprétée et le code HTML résultant sera placé dans le Body de la réponse HTTP. Ces vues sont templatables par du code Java mais c'est une mauvaise pratique car du code Java exprimant de la logique ne devrait pas résider dans une vue. C'est pourquoi il est recommandé d'utilise JSTL, la JSP Standard Tag Library.

Pour créer une JSP qui sera renvoyées par une servlet, créer un fichier .jsp à dans le dossier src/main/webapp/WEB-INF. Les fichiers de styles et de scripts sont à mettre directement dans le dossier src/main/webapp et sont accessibles à partir de l'URL racine du projet.

Il est possible de passer des objets Java à une JSP depuis une servlet :

request.setAttribute("nom", monObjet);

Pour renvoyer une JSP depuis une servlet, utilisez le code suivant :

this.getServletContext().getRequestDispatcher("/WEB-INF/maJsp.jsp").forward(request, response);

JSP Standard Tag Library

Installation

Pour installer JSTL, ajouter la dépendance au pom.xml de votre projet :

<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>jakarta.servlet.jsp.jstl</artifactId>
    <version>2.0.0</version>
</dependency>

Pour importer la JSTL dans une JSP utilisez les balises suivantes :

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

Balise de la JSTL

Afficher une variable

La balise <c:out> permet d'afficher une variable passée par la Servlet :

<c:out value="${ variable }">Valeur par défaut si la variable est null</c:out>

Cette balise évalue en fait une expression écrite dans un langage appelé EL pour Expression Language. C'est un langage simple qui permet de faire des petites opérations sur les variables. Il permet d'accèder par leur nom aux variables passés à la JSP par la servlet.

Il permet également d'accèder à un champs d'un objet Java si celui ci possède un Getter correctement nommé en utilisant l'opérateur .. EL supporte également les opérateurs arithmétiques et logique ainsi que les expressions ternaires. Le mot clé empty permet de vérifier si une valeur est nulle.

Itération sur une liste

La balise <c:forEach> permet d'itérer sur les éléments d'une liste :

<c:forEach items="${ maListe }" var="monElement" varStatus="status">
    <p>N°<c:out value="${ status.count }" /> : <c:out value="${ titre }" /> !</p>
</c:forEach>

Ici la variable monElement dansl'attribut var de la boucle correspond à l'élément courant de la liste. Quant à la variable status (optionnelle) définie dans l'attribut varStatus elle permet d'obtenir des informations sur l'élément courant de la boucle et possède des propriétés comme :

Rendu conditionnel

La balise <c:if> permet de faire du rendu conditionnel :

<c:if test="${ variable == '1' }">
    C'est vrai !
</c:if>

La contenu de la balise n'est rendu que si l'expression EL dans l'attribut test est évaluée à true.

Rendu conditionnel à choix multiple

La balise <c:choose> permet de faire du rendu conditionnel à choix multiples :

<c:choose>
    <c:when test="${ variable = '1' }">C'est égal à 1</c:when>
    <c:when test="${ variable = '2' }">C'est égal à 2</c:when>
    <c:when test="${ variable = '3' }">C'est égal à 3</c:when>
    <c:otherwise></c:otherwise>
</c:choose>
Base des applications JavaEE

JDBC

JDBC pour Java DataBase Connectivity est une spécification d'API pour accèder à une base de donnée relationnelle depuis une application Java. Pour se connecter à une base de donnée, il faut un Driver, qui implémente JDBC pour un moteur de base de donnée relationnelle donné. Par exemple il en existe un pour Oracle, un pour MySQL, pour PostgreSQL, etc ...

Installation du Driver

Pour installer le Driver JDBC pour MySQL, ajoutez la dépendance suivante dans votre pom.xml :

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.22</version>
</dependency>

Pour Oracle utilisez cette dépendance :

<dependency>
  <groupId>com.oracle.database.jdbc</groupId>
  <artifactId>ojdbc8</artifactId>
  <version>21.1.0.0</version>
</dependency>

N'oubliez pas refresh vos dépendances Maven après avoir modifié votre pom.xml !

Connexion à la base de donnée

Pour se connecter à une base de donnée utilisez la méthode static DriverManager.getConnexion() en passant en paramètre la chaine de caractère de connexion ainsi que les identifiants.

Pour MySQL :

Class.forName("com.mysql.cj.jdbc.Driver")
Connection connexion = DriverManager.getConnection("jdbc:mysql://localhost:3306/nomDeLaBase","root", "mot de passe");

Si MySQL vous lance une erreur de TimeZone, ajoutez ceci à la fin de l'URL JDBC : ?useLegacyDatetimeCode=false?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC.

Pour Oracle :

Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connexion = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","root", "mot de passe");

Il est recommandé de faire un singleton de l'instance de la connexion à la base de donnée.

Interactions avec la base de donnée

Requêtes

L'objet Statement permet d'exécuter des requêtes SQL :

Statement statement = connexion.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM Utilisateurs");

Le ResultSet est un itérateur sur les enregistrements contenues dans la réponse à la requête. On peut l'exploiter en itérant dessus grâce à la méthode next() :

while (rs.next()) {
  Utilisateur utilisateur = new Utilisateur();
  utilisateur.setPrenom(rs.getString("PRENOM"));
  utilisateur.setNom(rs.getString("NOM"));
  listeUtilisateur.add(utilisateur);
}

On peut en ensuite récupérer le contenu de l'enregistrement courant avec des méthodes comme getString(), getInteger(), etc ... en leur le passant le nom de la colonne dans la base.

Les classes Statement et ResultSet doivent toutes les deux êtres fermées après leur utilisation car il s'agit de ressources non managées. Pour une syntaxe concise on peut utiliser le "Try With Resource" :

try (Statement statement = this.connection.createStatement();
     ResultSet result = statement.executeQuery("SELECT * FROM Utilisateurs");) {
      while (result.next()) {
          Utilisateur utilisateur = new Utilisateur();
          utilisateur.setPrenom(rs.getString("PRENOM"));
          utilisateur.setNom(rs.getString("NOM"));
          listeUtilisateur.add(utilisateur);
      }
} catch (SQLException e) {
     System.err.println("Erreur à l'exécution de la requête SQL");
}

Requêtes DML

Pour modifier des données dans la base avec des commandes INSERT,UPDATE ou DELETE on utilise la méthode executeUpdate() de la classe Statement :

int modifiedRows = statement.executeUpdate("DELETE FROM Utilisateurs WHERE NOM = 'Shepard'");

executeUpdate() retourne le nombre de lignes modifiées par la requête.

SQL Dynamique

Il est possible de faire des requêtes préparées en utilisant la classe PreparedStatement :

PreparedStatement preparedStatement = connexion.prepareStatement("DELETE FROM Utilisateurs WHERE NOM = ?");

On peut ensuite affecter des paramètres :

preparedStatement.setString(1,"Shepard");

Enfin, on exécute le PreparedStatement en utilise soit executeUpdate() soit executeQuery() selon qu'il s'agisse d'une commande DML ou d'une requête.

Base des applications JavaEE

Filtres

Les filtres permettent d'intercepter des requêtes HTTP avant qu'elles arrives à leur Servlet afin d'exécuter de la logique. Cela est notamment utile pour restreindre l'accès à certaines routes à des utilisateurs non authentifiés ou ne possèdant pas le bon rôle.

Créer un filtre

Pour créer un filter, il faut créer une classe qui implémente l'interface Filter et annotées avec @WebFilter. En paramètre de l'annotation, il faut passer le paramètre urlPatterns, pour préciser les routes qui seront filtrée par ce filtre.

@WebFilter(urlPatterns = "/admin/*")
public class AdminFilter implements Filter {


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
      
    }

    public void init(FilterConfig config) throws ServletException {

    }
  
      public void destroy() {
    }

}

Par exemple ce filtre va intercepter les requêtes sur toutes les routes qui commencent par "/admin/".

Actions dans un filtre

Dans un filtre on peu récupérer la requête et la réponse HTTP par un simple cast :

var request = (HttpServletRequest) req;
var response = (HttpServletResponse) resp;

On peut ensuite accèder à la session pour vérfier des informations ou faire une redirection. Pour autoriser la poursuite de la requête normalement utilisez :

chain.doFilter();

Et pour retourner un code HTTP particulier (401 Unauthorized ou 403 Forbidden) et arrêter là la requête :

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;

Applications JavaEE Avancées

Développement d'applications plus orientées entreprise avec l'utilisation des Entreprise Java Beans, de la Java Persistence API et en utilisant le serveur d'application JBoss WildFly

Applications JavaEE Avancées

Serveur d'application Wildfly

Jusqu'ici, nous n'avions besoin que d'un conteneur de servlet pour exécuter nos applications, c'est pourquoi Tomcat nous suffisait. Cependant, pour utiliser les spécifications JakartaEE que nous allons étudier dans cette partie, il faut disposer d'un serveur d'application JakartaEE complet. Nous allons ici utiliser Wildfly (évolution de JBoss).

Installation

Commencez donc par télécharger Wildfly 23 sur ce lien. Décompressez ensuite le ZIP téléchargé et placez le à l'emplacement où vous rangez vos outils.

Créer un Projet avec WildFly

Pour créer un nouveau projet sur votre nouveau serveur Wildfly, utilisez le menu "New..." :

Capture d’écran 2021-05-23 104616.png

Indiquez ensuite le dossier d'installation de votre serveur :

Capture d’écran 2021-05-23 104640.png

Vous pouvez désormais le séléctionner dans le menu déroulant :

Capture d’écran 2021-05-23 104459.png

Applications JavaEE Avancées

Enterprise Java Beans

Les EJB pour Enterprise Java Beans sont un standard de la pateforme JakartaEE pour créer des composants serveur. Cette architecture propose un conteneur pour créer des composants Java distribués hébergés sur un serveur d'application.

Il existe plusieurs types d'EJB.

Entity

Les EJB Entity permettent de représenter les données manipulées par l'application. On en parlera plus en détail dans le chapitre suivant sur JPA.

Message

Les EJB Message Driven servent à accomplir des tâches de manière asynchone en utilisant des mécanismes de file de message.

Session

Les EJB session permettent de proposer des services avec ou sans état. Il pour être injectés par le conteneur EJB à un service local (comme une servlet par exemple), mais il peut aussi être appelé par un client distant, via le protocole RMI (Remote Method Invocation).

EJB Session

Interface

Pour définir un EJB session, il faut définir une interface qui définir les méthodes accessibles par le code client. Il faut y ajouter une interface pour signaler au conteneur EJB qu'il s'agit d'une interface d'EJB ; @Remote pour un accès par un client RMI distant, et @Local pour un accès par un composant local déployé sur le serveur également.

@Remote
public interface HelloWorldRemote {
    String helloWorld(String name);
}
@Local
public interface HelloWorldLocal {
    String helloWorld(String name);
}

Implémentation

On peut ensuite créer une classe EJB qui implémente cette interface. On doit utiliser une annotation pour signaler au conteneur qu'il s'agit d'une classe d'implémentation EJB. On utilise @Stateless pour un composant qui sera instancié à chaque demande. Le paramètre d'annotation mappedName permet de préciser un nom dans l'annuaire JNDI qui va référencer tous les EJB sur serveur.

@Stateless(mappedName="HelloWorld")
public class HelloWorld implements HelloWorldRemote {
    public String helloWorld(String name) {
        return "Hello, " + name;
    }
}

Pour avoir toujours la même instance du composant fournie par le conteneur pour une session de client donnée, on peut utiliser à la place @Stateful.

@Stateful(mappedName="HelloWorld")
public class HelloWorld implements HelloWorldRemote {
    public String helloWorld(String name) {
        return "Hello, " + name;
    }
}

Utilisation

Pour demander au conteneur d'EJB d'injecter un EJB dans un autre composant, comme un autre EJB, ou une Servlet par exemple, utilisez l'annotation @EJB sur un attribut du type d'une interface EJB locale :

@EJB
private HelloWorldLocal helloWorld;

Client RMI

On peut accèder aux EJB remote via la protocole RMI depuis un client distant Java. Après avoir lancé le serveur Wildfly qui possède des EJB Remote, on peut regarder ensuite les logs du serveur Wildfly dans l'interface d'IntelliJ IDEA, et y voir une ligne affirmant que l'EJB a bien été detecté et ajouté à l'annuaire JNDI. Copiez l'URL qui commence par EJB et gardez là dans un bloc note, elle sera utile plus tard.

Capture d’écran 2021-05-23 143204.png

Configuration du projet Client RMI

Pour le client EJB, créez un nouveau projet IntelliJ, cette fois en utilisant le template Maven et en utilisant l'archétype Quickstart. Une fois votre projet généré, rajoutez la dépendance suivante dans votre fichier pom.xml :

<dependency>
  <groupId>org.wildfly</groupId>
  <artifactId>wildfly-ejb-client-bom</artifactId>
  <version>21.0.0.Final</version>
  <type>pom</type>
</dependency>

Mettez à jours vos dépendances Maven, puis ajoutez une Run Config d'application Java normale.

Récupérer les EJB

Dans un premier temps, il faut copier le package qui contient les interfaces dans le projet client (les classes doivent avoir le même nom et le même package dans le projet client). Retirez dans le projet client les annotations @Remote des interfaces.

Dans la méthode main ajoutez d'abord le code suivant, afin de configurer la connexion EJB :

final Hashtable<String, String> jndiProperties = new Hashtable<>();
jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
if(Boolean.getBoolean("http")) {
  jndiProperties.put(Context.PROVIDER_URL,"http://localhost:8080/wildfly-services");
} else {
  jndiProperties.put(Context.PROVIDER_URL,"remote+http://localhost:8080");
}
final Context context = new InitialContext(jndiProperties);

Ensuite pour récupérer votre EJB, ajouter ce code, en modifiant la string passée à la méthode lookup() par l'URL ejb que nous avons noté tout à l'heure :

final GestionContactRemote ejb = (GestionContactRemote) context.lookup("ejb:/tuto-jakarta-1.0-SNAPSHOT/HelloWorld!fr.arsenelapostolet.tuto_jakarta.HelloWorldRemote");

Vous pouvez enfin tester votre EJB :

System.out.println(ejb.helloWorld("John Shepard"));

Si tout s'est bien passé, vous devriez observer les logs du client EJB dans votre console ainsi que votre afichage :

Capture d’écran 2021-05-23 143923.png

Applications JavaEE Avancées

Persistance avec JPA

JPA pour Java Persistance API est la spécification ORM de la plateforme JavaEE. Un ORM, pour Object Relationnal Mapper, est un composant logiciel chargé de faire le lien entre le modèle objet et le modèle relationnel et permet ainsi de persister des classes Java dans une système de gestion de base de donnée relationnel sans écrire tout le code JDBC directement. Le moteur vas ainsi établir et exécuter les opérations JDBC nécéssaires. JPA étant une spécification, on a besoin de choisir une implémentation pour l'utiliser. Nous allons utiliser la plus connue, Hibernate, mais il en existe d'autres, comme EclipseLink, TopLink, etc ...

Dépendances

Pour utiliser JPA avec Hibernate, il faut rajouter ces dépendances à notre projet :

<dependency>
  <groupId>jakarta.persistence</groupId>
  <artifactId>jakarta.persistence-api</artifactId>
  <version>2.2.3</version>
</dependency>

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>5.4.22.Final</version>
</dependency>

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-entitymanager</artifactId>
  <version>5.4.22.Final</version>
</dependency>

Data Source

Pour pouvoir accèder à une base donnée avec JPA, il faut configurer une Datasource sur notre serveur d'application. Pour cela, nous allons devoir accèder à l'interface d'administration de notre serveur Wilfly.

Créer un utilisateur d'Administration sur Wildfly

Rendez vous dans WILDFLY_HOME/bin et exécutez :

./add-user.bat

Suivez ensuite les instruction pour créér un utilisateur. Lorsqu'on l'on vous demande le type d'utilisateur choisissez Utilisateur d'administration.

Vous pouvez ensuite accèder au panneau d'administration Wildfly via ce lien (Le serveur Wildfly doit être démarré). Les identifiants pour se connecter sont ceux donnés à la création de l'utilisateur.

Configurer le Driver

Commencez par télécharger le Driver JDBC MySQL sur ce lien, décompressez le et récupérez le fichier JAR.

Ouvrez maintenant l'interface d'administration à http://localhost:9990 et rendez vous dans l'onglet Deployement. Ajoutez ensuite un nouveau déploiement en cliquant sur le bouton + puis Upload Deployment:

Capture d’écran 2021-01-16 222145.png

Dans la fenêtre qui s'ouvre, uploadez votre JAR JDBC et validez. Si tout ce passe bien il devrait être répertorié ici dans les déploiements :

Capture d’écran 2021-01-16 222230.png

Ainsi qu'ici dans l'onglet Configuration sous Configuration -> Subsystems -> Datasources & Drivers -> JDBC Drivers.

Capture d’écran 2021-01-16 222742.png

Configurez la source de donnée

Sur l'interface d'administration de Wildfly, allez à Configuration -> Subsystems -> Datasources & Drivers -> Datasources et utilisez le boutons + et New Datasource*. Suivez ensuite les instructions du wizard en renseignant les informations.

Choisir notre SGBD : Capture d’écran 2021-01-16 223229.png

Dans attributes, vous pouvez choisir un nom pour votre Datasource ainsi que son adresse dans l'annuaire JNDI.

Choisir le driver que nous avons déployé : Capture d’écran 2021-01-16 223309.png

Pour les identifiants de votre base de donnée voici comment les renseigner :

Vous pouvez utiliser le bouton Test Connection pour vérifier que les information son correctes.

Il m'est déjà arrivé que le bouton Test Connection ne marche pas mais que la datasource soit tout de même bien configurée et fonctionnelle

Si au moment du déploiment de votre artéfact sur le serveur MySQL vous lance une erreur de TimeZone, ajoutez ceci à la fin de l'URL JDBC : ?useLegacyDatetimeCode=false?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC. Vous pouvez modifier votre datasource existante en allant dans Configuration -> Subsystems -> Datasources & Drivers -> Datasources Et en faisant "View" sur votre datasource, puis onglet "Connection" puis "Edit"

Utiliser le nouvelle source de donnée

Dans votre projet, sous le dossier resources, créez un dossier, META-INF et dans ce dernier un fichier, persistence.xml, et entrer ce contenu pour référencer votre datasource dans votre projet :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">
    <persistence-unit name="default">
        <jta-data-source>java:/MySqlDS</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MariaDBDialect"/>
        </properties>
    </persistence-unit>
</persistence>

Voilà, votre datasource est configurée, nous allons pouvoir maintenant étudier plus en détail les outils proposés par JPA.

EJB Entity

Les EJB Entity sont des EJB particuliers qui servent à représenter des données. Pour créer un EJB entité, il faut créer une classe annotée avec l'annotation @Entity. Une classe entité doit avoir un attribut annoté avec @Id qui correspondra à la clé primaire dans la table de la base de donnée. Chaque propriété de l'entité sera mappée à la colonne de la base de donnée. Les autres champs de la classe entités sont persistés automatiquement. Pour qu'un champs ne soit pas persisté, il faut l'annoter avec @Transient.

Exemple d'entité :


@Entity
public class Todo {
  	@Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
  
  	private String title;
    private String description;
  
  	... constructeur & getters & setters ...
}

Ici l'annotation @GeneratedValue(strategy= GenerationType.AUTO) permet de générer automatiquement l'id avec un auto-incrément.

On peut aussi définir des contraintes sur les colonnes en rajoutant l'annotation @Column sur le champs et en lui passant des paramètres, par exemple :

@Column(unique = true, length = 32)

Cela permet de mettre la contraint "unique" sur le champs et d'imposer une longueur maximum de 32 caractère.

Entity Manager

L'entity manager est une interface qui permet d'intéragir avec les entités :

Pour l'obtenir dans une classe, demandez le avec l'annotation @PersistenceContext :

public class TodoRepository {
    @PersistenceContext
    private EntityManager entityManager;
}

L'entity manager vas vous permettre de faire toutes les opérations nécessaires sur les Entity.

Persister une Entity

Utilisez la méthode persist() :

entityManager.persist(new Todo("test title", "test content"));

Supprimer une Entity

Utilisez la méthode remove() :

entityManager.remove(myEntity);

Récupérer une Entity

Pour récupérer une entité à partir de son Id :

Todo todo = entityManager.find(Todo.class, todoId);

Créer une Requête JPQL

JPQL pour Java Persistence Query Language est un langage de requête orienté objet qui permet de créer des requêtes sur des entités. Par exemple :

List<Todo> todos = entityManager.createQuery("SELECT t FROM Todo t", Todo.class).getResultList();

Ou alors une requête paramètrée :

String title = "test title";
List<Todo> todos = entityManager.createQuery("SELECT t FROM Todo t WHERE t.title = :title", Todo.class)
  .setParameter("title", title)
  .getResultList();

Relations

Les relations permettent de faire des liens entre les entités. Il existe quatres types de relations, définies par des annotations :

Exemple :

@Entity
public class Todo {
  ... autres propriétés ...
  
  @ManyToOne
  private ApplicationUser owner;
  
  ... getters & setters ....
}
@Entity
public class ApplicationUser {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;
  
  	private String name;
    
  	@OneToMany(
    	cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private Set<Todo> todos;
  
    ... getters & setters ...
}

Les annotations de relation possèdes plusieurs paramètres utiles à connaitres :

Applications JavaEE Avancées

WebServices JAX-RS

g