Comment accéder à une base de données pour effectuer des recherches au moyen du module “Data” de Apache Deltaspike et OpenJPA?

Introduction

Dans l’épisode 2, on a vu que Deltaspike permettait facilement l’injection de ressources depuis un fichier de configuration. Dans troisième volet, on va voir comment accéder à une base de données pour effectuer des recherches au moyen du module “Data” de Apache Deltaspike et OpenJPA.

GitHub

Afin de faciliter l’utilisation de cet exemple, j’ai préparé un fichier zip avec les fichiers nécessaires. C’est par ici: ApacheDeltaspikeSkeleton.

Configuration

Les dépendances

Dans le fichier pom.xml, il va falloir ajouter plusieurs dépendances:

    
 1<dependency>
 2            <groupId>org.apache.deltaspike.modules</groupId>
 3            <artifactId>deltaspike-data-module-api</artifactId>
 4            <version>${deltaspike.version}</version>
 5            <scope>compile</scope>
 6        </dependency>
 7        <dependency>
 8            <groupId>org.apache.deltaspike.modules</groupId>
 9            <artifactId>deltaspike-data-module-impl</artifactId>
10            <version>${deltaspike.version}</version>
11            <scope>runtime</scope>
12        </dependency>
13        <dependency>
14            <groupId>org.apache.deltaspike.modules</groupId>
15            <artifactId>deltaspike-jpa-module-api</artifactId>
16            <version>${deltaspike.version}</version>
17            <scope>compile</scope>
18        </dependency>
19        <dependency>
20            <groupId>org.apache.deltaspike.modules</groupId>
21            <artifactId>deltaspike-jpa-module-impl</artifactId>
22            <version>${deltaspike.version}</version>
23            <scope>runtime</scope>
24        </dependency>

Pour OpenJPA, il n’y a rien à ajouter puisque j’ai choisi d’utiliser Apache TomEE (voir l’article consacré).

La base de données

Il faut ajouter le fichier WEB-INF/resources.xml. Le contenu est adapté à MariaDB et à mon exemple. C’est grâce à ce fichier que OpenJPA va être capable de se connecter à la base.

    
1<?xml version="1.0" encoding="iso-8859-1" ?>
2<resources>
3    <Resource id="maBase" type="javax.sql.DataSource">
4        JdbcDriver org.mariadb.jdbc.Driver
5        JdbcUrl jdbc:mysql://localhost:3306/baseTest
6        UserName userTest
7        Password XXXXXX
8    </Resource>
9</resources>

Les paramètres importants sont:

  • id: cet identifiant est libre et sera ensuite utilisé lors de la définition de l’unité de persistance.
  • JdbcDriver: il s’agit de la classe permettant d’accéder à la base de données.
  • JdbcUrl: c’est l’url de connexion à la base
  • UserName: il faut indiquer un utilisateur de la base de données pouvant lire, écrire et supprimer des données.
  • Password: je te laisse deviner!

Ensuite, il faut ajouter le fichier META-INF/persistence.xml. Ce fichier fait le lien entre la datasource définie précédemment et l’EntityManager nécessaire pour établir les transactions d’accès. Son contenu est comme suit:

    
 1<persistence
 2    version="2.0"
 3    xmlns="http://java.sun.com/xml/ns/persistence"
 4    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
 6    <persistence-unit name="GtgPersistenceUnit" transaction-type="JTA" persistence-unit-caching-type="NONE">
 7        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
 8        <jta-data-source>maBase</jta-data-source>
 9        <properties>
10            <property name="openjpa.Log" value="SQL=Trace" />
11            <property name="openjpa.ConnectionFactoryProperties" value="PrintParameters=true" />
12        </properties>
13    </persistence-unit>
14</persistence>

Le détail des paramètres:

  • name: il s’agit du nom de l’unité de persistance qui permettra de faire lien avec l’EntityManager.
  • jta-data-source: ici on donne le même nom que pour “id” dans le fichier resources.xml
  • openjpa.Log: avec “SQL=Trace”, OpenJpa va logger les queries qui seront effectuées. Très pratique en debug.
  • openjpa.ConnectionFactoryProperties: avec “PrintParameters=true”, les paramètres de queries seront également loggés. C’est très pratique en debug MAIS il faut absolument le virer pour une application en production (on risquerait d’afficher des informations nominatives dans un log…)

Un peu de code!

Permettre à Deltaspike de créer l’EntityManager

Il faut ajouter une classe permettant de produire un EntityManager à partir de l’unité de persistance:

    
 1package ch.gobothegeek.database;
 2
 3import javax.enterprise.context.ApplicationScoped;
 4import javax.enterprise.inject.Disposes;
 5import javax.enterprise.inject.Produces;
 6import javax.persistence.EntityManager;
 7import javax.persistence.EntityManagerFactory;
 8import javax.persistence.PersistenceUnit;
 9import javax.transaction.TransactionScoped;
10import java.io.Serializable;
11
12@ApplicationScoped
13public class GtgEntityManagerProducer implements Serializable {
14    @PersistenceUnit(name = "GtgPersistenceUnit") private EntityManagerFactory entityManagerFactory;
15
16    @Produces
17    @TransactionScoped
18    public EntityManager create() {
19        return this.entityManagerFactory.createEntityManager();
20    }
21
22    public void close(@Disposes EntityManager em)
23    {
24        if (em.isOpen()) { em.close(); }
25    }
26
27    public EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; }
28    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; }
29}

Ajouter un repository Deltapsike

Il s’agit d’une classe permettant les queries pour un type d’entité donné.

    
 1package ch.gobothegeek.repository.user;
 2
 3import ch.gobothegeek.model.user.User;
 4import ch.gobothegeek.model.user.UserPK;
 5import org.apache.deltaspike.data.api.*;
 6import javax.enterprise.context.ApplicationScoped;
 7import javax.transaction.Transactional;
 8import java.util.Optional;
 9
10@ApplicationScoped
11@Transactional
12@Repository(forEntity = User.class)
13public interface UserRepository extends extends FullEntityRepository<User, UserPK>, EntityManagerDelegate<User>, CriteriaSupport<User> {
14
15    @Transactional
16    Optional<User> findByLogin(String login);
17
18    @Transactional
19    @Query("SELECT u FROM User u WHERE u.login = ?1 AND u.password = ?2 AND u.enabled = true")
20    Optional<User> checkLogin(String username, String hashPwd);
21}

Le repository Deltaspike a un côté magique. Chez Apache, ils ont prévu une norme pour nommer les méthodes et effectuer les recherches en conséquence. La méthode “findByLogin” va générer automatiquement la requête “SELECT * FROM baseTest.table_user WHERE login=X” (avec X remplacé par la valeur de la variable login reçue en paramètre). Du coup, pour des recherches simples, on ne s’embête pas avec du JPQL.

La deuxième méthode “checkLogin ne respecte par la norme et il faut donc lui indiquer la query a exécuter. La première option (que je conseille) est d’utiliser la syntaxe JPQL, simplement parce que cela permet de ne pas se soucier de la base de données utilisée. L’autre option est d’écrire une requête native, qui sera sans doute plus performante mais qui posera souci en cas de changement de base (ou dans des environnements mixtes, genre un mariaDB en dev et un oracle en production -si si ça existe-).

Dernier point à noter: “Optional” indique que la requête peut ne rien retourner. Si on ne précise pas “Optional” et que la requête ne trouve rien, on reçoit une exception. Même chose si la requête retourne plusieurs résultats alors qu’on s’attendait à un seul.

Utiliser le repository

J’ai ajouté un service afin de faire le lien entre mon WebService (on en parlera dans un autre article) et mon repository.

    
 1package ch.gobothegeek.service.user;
 2
 3import ch.gobothegeek.model.user.User;
 4import ch.gobothegeek.model.user.UserPK;
 5import ch.gobothegeek.repository.user.UserRepository;
 6import org.apache.logging.log4j.LogManager;
 7import org.apache.logging.log4j.Logger;
 8import javax.enterprise.context.ApplicationScoped;
 9import javax.enterprise.context.Initialized;
10import javax.enterprise.event.Observes;
11import javax.inject.Inject;
12import javax.transaction.Transactional;
13import java.util.Optional;
14
15@ApplicationScoped
16@Transactional
17public class UserService extends OpService<User, UserPK> {
18    private final Logger logger = LogManager.getLogger(UserService.class);
19
20    @Inject private UserRepository userRepo;
21
22    public UserService() { }
23
24    public Boolean checkUser(String login, String pwd) {
25        Optional<User> optUser;
26
27        optUser = this.userRepo.checkLogin(login, pwd);
28        return (optUser.isPresent());
29    }
30}

Le service reçoit le repository par injection. Dans la méthode “checkUser”, on appelle la méthode “checkLogin” du repository (c’est magique puisque le repository est une interface et que la méthode ne contient aucun code). Le retour est un “Optional” qui permet de savoir si la requête a retourné une entité ou non.

Conclusion

Dans ce troisième chapitre, on vient de voir que Apache Deltaspike permet d’utiliser facilement OpenJPA pour établir une connexion à une base de données et effectuer des requêtes.