Comment améliorer les recherches avec Apache Deltaspike Data sans écrire du code JPQL? Éléments de réponse.

Introduction

Dans l’article précédent, on a vu comment mettre en œuvre l’accès à la base de données avec Deltaspike Data. Ici nous allons voir comment améliorer les recherches et la gestion des transactions. N’oubliez pas de consulter le fichier zip “ApacheDeltaspikeSkeleton”.

Amélioration des recherches

A partir d’un repository (basé sur FullEntityRepository ou EntityRepository par exemple), on a vu la facilité à écrire des recherches par un ou plusieurs champs, avec retour obligatoire (sous peine de recevoir une exception). C’est bien sûr utile mais plutôt incomplet. Apache Deltaspike propose deux axes d’amélioration:

  • le retour optionnel
  • la limitation des résultats

Query à retour optionnel

Par exemple, la méthode “abstract User findByLogin(string login)” retournera soit un objet User dont le login est celui demandé soit lèvera une exception. Si de nombreux cas doivent être traités de la sorte, on imagine bien que dans d’autres cas, l’absence d’enregistrement n’est pas une erreur, comme dans le cas d’un processus d’authentification, le login risque de ne pas exister (on pense avoir un compte, on se trompe de login, etc). On va alors introduire la classe “Optional”, ce qui transforme notre méthode comme ceci: “abstract Optional findByLogin(String login)”. L’utilisation se fait alors comme ceci:

    
1Optional<User> optUser = this.rdpository.findByLogin("test"); 
2if (optUser.isPresent()) { 
3	System.out.println("User found, id=" + optUser.get().getId()); 
4} else { 
5	System.out.println("user not found"); 
6}

Je trouve le code plus lisible avec l’utilisation d’Optional et un peu plus logique quand on sait qu’on peut ne pas avoir de résultat.

Limitation des résultats

Dans la norme JPA, il n’est pas possible de limiter les résultats avec le mot clé SQL “limit”. Impossible donc de trouver 10 enregistrements à partir du 8ème par exemple. Apache DeltaSpike propose une implémentation plutôt bien faite pour contourner ce problème puisqu’il suffit d’ajouter deux paramètres à la méthode: @FirstResult et @MaxResults.
Par exemple, si on veut afficher une liste d’utilisateurs avec pagination, on va ajouter la méthode “abstract Collection findUserOrderByName(@FirstRsultat int start, @MaxResults int max)” et on l’utilisera comme ceci:

    
1Collection users = this.repository.findUserOrderByName(8, 10);

Problèmes rencontrés

@Query en update

Dans le cadre de mon projet, j’ai dû mettre à jour l’enregistrement de l’utilisateur qui venait de se connecter. Dans mon repository, je disposais d’une première méthode:

    
1@Transactional @Query("SELECT User u WHERE u.login=?1 AND u.active=true") 
2abstract Optional<User> findUser(String login);

Ensuite, pour mettre à jour l’enregistrement, j’ai ajouté cette méthode:

    
1@Transactional 
2@Query("UPDATE User u SET u.lastlogin = ?1 WHERE u.login = ?2") 
3abstract User updateLastLogin(Date last, String login);

finalement, j’ai agencé le code de cette manière:

    
1Optional<User> optUser = this.repository.findUser(login); 
2if (optUser.isPresent()) { this.repository.updateLastLogin(new Date(), login); }

Eh bien le code explose avec une erreur de transaction. J’ai testé les options du @Transactional, sans succès. Au final, la solution est d’écrire les méthodes de cette manière:

    
1@Transactionalabstract Optional findByUserAndActive(String login, Boolean active);
    
1@Transactionalpublic User updateLastLogin(Date last, User user) {
2	user.setLastLogin(last);
3	user = this.save(user);
4	return user;
5}

et on l’utilise comme suit:

    
1Optional optUser = this.repository.findByUserAndActive(login, true);
2if (optUser.isPresent()) { this.repository.updateLastLogin(new Date(), optUser.get()); }

En gros, impossible de mixer des queries SELECT et UPDATE / DELETE, il faut passer par les méthodes exposées par le repository.

Gestionnaire de transactions non pris en compte

Après avoir configuré la base de données, les repositories et les services (voir épisode 3), tout semblait fonctionner parfaitement, les premières requêtes de type SELECT s’exécutaient sans souci. Mais hélas, lors du premier UPDATE, le gestionnaire de transaction (TransactionManager) indique qu’aucune transaction n’est débutée. C’est un peu la douche froide. Après pas mal de recherches, la solution est finalement plutôt simple puisqu’il faut ajouter une option dans le fichier WEB-INF/beans.xml et annoter les méthodes d’update @Transactional(Transactional.TxType.REQUIRED).

Fichier WEB-INF/beans.xml

    
1<alternatives>
2    <class>org.apache.deltaspike.jpa.impl.transaction.ContainerManagedTransactionStrategy</class>
3</alternatives>

Méthode annotée

    
1@Transactional(Transactional.TxType.REQUIRED)
2public User registerAccount(String username, String pwd) {
3.......
4}

Conclusion

Apache Deltaspike propose un système de requête bien fait et qui élimine (sans retirer la possibilité) le recours systématique à l’écriture des requêtes JPQL.