Dans ce quatrième article, nous allons nous intéresser aux repositories de l’application.
Persistance des entités
Dans le troisième article, j’ai présenté les entités de l’application. Apache Deltaspike propose une solution simple (et plutôt élégante) permettant de gérer la persistance des données: le repository. En effet, les méthodes abstraites répondent à une normalisation permettant de ne pas écrire la requête JPQL correspondante.
Repository générique: LfdRepository
Afin de faciliter l’écriture des différents repositories, j’ai ajouté un repository générique: LfdRepository.
1package ch.gobothegeek.lofidrox.repositories;
2
3import org.apache.deltaspike.data.api.EntityManagerDelegate;
4import org.apache.deltaspike.data.api.EntityPersistenceRepository;
5import org.apache.deltaspike.data.api.criteria.CriteriaSupport;
6import java.io.Serializable;
7
8// describe a generice repository.
9// Please note that this repository is NOT annotated with @Repository (otherwise it will be used for queries, leading to errors)
10public abstract class LfdRepository<E, P extends Serializable> implements EntityPersistenceRepository<E, P>, EntityManagerDelegate<E>, CriteriaSupport<E> { }
Repository FileDescriptorRepository
Ce repository permet la manipulation des entités FileDescriptor.
1package ch.gobothegeek.lofidrox.repositories;
2
3import ch.gobothegeek.lofidrox.model.entities.FileDescriptor;
4import ch.gobothegeek.lofidrox.model.entities.FileDescriptorPK;
5import ch.gobothegeek.lofidrox.services.FileDescriptorService;
6import org.apache.deltaspike.data.api.Repository;
7import org.slf4j.Logger;
8import org.slf4j.LoggerFactory;
9import javax.enterprise.context.ApplicationScoped;
10import javax.inject.Inject;
11import javax.transaction.Transactional;
12import java.util.Date;
13import java.util.List;
14import java.util.Optional;
15
16// repository used to access files table
17@ApplicationScoped
18@Repository(forEntity = FileDescriptor.class)
19public abstract class FileDescriptorRepository extends LfdRepository<FileDescriptor, FileDescriptorPK> {
20 private final Logger logger = LoggerFactory.getLogger(FileDescriptorRepository.class);
21
22 @Inject private FileRecipientRepository fileRecipientRepository;
23 @Inject private FileDescriptorService fileDescriptorService;
24
25 // return file by its name
26 @Transactional(Transactional.TxType.REQUIRED)
27 public abstract Optional<FileDescriptor> findById(Integer id);
28
29 @Transactional(Transactional.TxType.REQUIRED)
30 public abstract List<FileDescriptor> findByIdIn(List<Integer> ids);
31
32 // create the required file
33 @Transactional(Transactional.TxType.REQUIRED)
34 public FileDescriptor createFile(String filename, String path, String source, String type) {
35 FileDescriptor file;
36
37 file = new FileDescriptor(null, filename, path, source, new Date(), type);
38 file = this.save(file);
39 return file;
40 }
41
42 // update file path
43 @Transactional(Transactional.TxType.REQUIRED)
44 public FileDescriptor updateFilePathAndType(Integer id, String path, String type) {
45 Optional<FileDescriptor> file;
46
47 file = this.findById(id);
48 if (file.isPresent()) {
49 file.get().setPath(path);
50 file.get().setDataType(type);
51 this.save(file.get());
52 return file.get();
53 }
54 return null;
55 }
56
57 // delete specified file
58 @Transactional(Transactional.TxType.REQUIRED)
59 public void deleteFile(Integer id) {
60 Optional<FileDescriptor> file;
61
62 file = this.findById(id);
63 file.ifPresent(this::remove);
64 }
65}
Méthode findById
Pas grand chose à en dire: il s’agit d’une recherche par identifiant.
Les deux points à noter sont que la méthode est abstract
et que son retour est Optional
afin d’indiquer que l’entité peut ne pas exister.
Méthode findByIdIn
Il s’agit d’une extension de la méthode précédente puisqu’elle permet la recherche des entités dont l’identifiant figure dans la liste indiquée.
Méthode createFile
Ici la méthode n’est pas abstraite car elle va écrire en base une nouvelle entité.
Méthode updateFilePathAndType
Cette méthode va mettre à jour certains champs de l’entité désignée par son identifiant.
Méthode deleteFile
Cette méthode permet la suppression de l’entité désignée par son identifiant.
Point notable: file.ifPresent(this::remove);
. Si la méthode findById
ne retourne rien, la suppression n’est pas tentée. Cette syntaxe permet d’éviter d’écrire ce code:
1if (file.isPresent()) { this.remove(file); }
Repository FileRecipientRepository
Ce repository permet la manipulation des entités FileRecipient.
1package ch.gobothegeek.lofidrox.repositories;
2
3import ch.gobothegeek.lofidrox.model.entities.FileRecipient;
4import ch.gobothegeek.lofidrox.model.entities.FileRecipientPK;
5import org.apache.deltaspike.data.api.Query;
6import org.apache.deltaspike.data.api.QueryParam;
7import org.apache.deltaspike.data.api.Repository;
8import org.slf4j.Logger;
9import org.slf4j.LoggerFactory;
10import javax.enterprise.context.ApplicationScoped;
11import javax.transaction.Transactional;
12import java.util.List;
13import java.util.Optional;
14
15// repository used to access files recipients table
16@ApplicationScoped
17@Repository(forEntity = FileRecipient.class)
18public abstract class FileRecipientRepository extends LfdRepository<FileRecipient, FileRecipientPK> {
19 private final Logger logger = LoggerFactory.getLogger(FileRecipientRepository.class);
20
21 // return recipient by its id
22 @Transactional(Transactional.TxType.REQUIRED)
23 public abstract Optional<FileRecipient> findById(Integer id);
24
25 // return recipients for a file
26 @Transactional(Transactional.TxType.REQUIRED)
27 public abstract List<FileRecipient> findAnyByFileId(Integer id);
28
29 // return recipient for a file and a user
30 @Transactional(Transactional.TxType.REQUIRED)
31 public abstract Optional<FileRecipient> findAnyByUserToAndFileId(String userTo, Integer id);
32
33 // create the required file
34 @Transactional(Transactional.TxType.REQUIRED)
35 public FileRecipient createLink(Integer fileId, String userTo, Boolean downloaded) {
36 FileRecipient file;
37
38 file = new FileRecipient(null, fileId, userTo, downloaded);
39 file = this.save(file);
40 return file;
41 }
42
43 // return list of files for a specific user
44 @Transactional(Transactional.TxType.REQUIRED)
45 @Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file f WHERE rec.userTo = :uto ORDER BY f.name")
46 public abstract List<FileRecipient> listFilesForUser(@QueryParam("uto") String userTo);
47
48 // delete specified file
49 @Transactional(Transactional.TxType.REQUIRED)
50 public void deleteLink(String user, Integer id) {
51 Optional<FileRecipient> recipient;
52
53 recipient = this.findAnyByUserToAndFileId(user, id);
54 recipient.ifPresent(this::remove);
55 }
56
57 // return file for a specific id and user
58 @Transactional(Transactional.TxType.REQUIRED)
59 @Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file file WHERE rec.userTo = :uto AND file.id = :fid")
60 public abstract Optional<FileRecipient> findFileByIdAndUserTo(@QueryParam("fid") Integer id, @QueryParam("uto") String userTo);
61
62 // mark file read
63 @Transactional(Transactional.TxType.REQUIRED)
64 public FileRecipient markRead(FileRecipient recipient) {
65 if (null != recipient) {
66 recipient.setDownloaded(true);
67 recipient = this.save(recipient);
68 }
69 return recipient;
70 }
71}
Méthode findById
Retourne l’entité désignée par son identifiant.
Méthode findAnyByFileId
Cette méthode retourne la liste des entités liées au FileDescriptor dont l’identifiant est indiqué.
Méthode findAnyByUserToAndFileId
Même concept que la méthode précédente mais avec l’identifiant de l’entité User en plus du FileDescriptor.
Méthode createLink
Permet d’ajouter un lien entre une entité User et une entité FileDescriptor. C’est grâce à cela qu’on peut envoyer le même fichier à plusieurs destinataires.
Méthode listFilesForUser
Cette méthode est particulière: elle est abstraite mais il n’est pas possible de décrire la requête avec les mots clefs proposés par Deltaspike. Il faut alors ajouter cette requête avec l’annotation @Query
.
Méthode deleteLink
Cette méthode permet d’effacer le lien entre une entité User et une entité FileDescriptor.
Méthode findFileByIdAndUserTo
Elle permet de trouver le FileDescriptor demandé pour le User indiqué. Même remarque que pour listFilesForUser
.
Méthode markRead
Ici on va noter que le fichier a été téléchargé par son destinataire.
Repository UserRepository
Ce repository assure l’accès aux comptes utilisateurs (création, connexion) via l’entitét User
1package ch.gobothegeek.lofidrox.repositories;
2
3import ch.gobothegeek.lofidrox.model.entities.User;
4import ch.gobothegeek.lofidrox.model.entities.UserPK;
5import org.apache.deltaspike.data.api.Repository;
6import org.slf4j.Logger;
7import org.slf4j.LoggerFactory;
8import javax.enterprise.context.ApplicationScoped;
9import javax.transaction.Transactional;
10import java.util.List;
11import java.util.Optional;
12
13// repository used to access users table
14@ApplicationScoped
15@Repository(forEntity = User.class)
16public abstract class UserRepository extends LfdRepository<User, UserPK> {
17 private final Logger logger = LoggerFactory.getLogger(UserRepository.class);
18
19 // return user if found by its username
20 @Transactional(Transactional.TxType.REQUIRED)
21 public abstract Optional<User> findByUsername(String username);
22
23 // with username and encoded password, returns the required user (if found)
24 @Transactional(Transactional.TxType.REQUIRED)
25 public abstract Optional<User> findByUsernameAndPwd(String username, String pwd);
26
27 // create the required user, with encrpyted password
28 @Transactional(Transactional.TxType.REQUIRED)
29 public User createUser(String username, String pwd) {
30 User user;
31
32 user = new User(username, pwd);
33 user = this.save(user);
34 return user;
35 }
36
37 // return list of users
38 @Transactional(Transactional.TxType.REQUIRED)
39 public abstract List<User> findAllOrderByUsernameAsc();
40}
Méthode findByUsername
Rien de spécial puisque cette méthode retourne un User grâce à son username.
Méthode findByUsernameAndPwd
Cette méthode est utilisée lors de la phase de connexion: elle permet de vérifier que le couple nom d’utilisateur / mot de passe (chiffré!) est le bon.
Méthode createUser
Cette méthode permet d’ajouter un nouvel utilisateur.
Méthode findAllOrderByUsernameAsc
Encore une méthode abstraite. Son objectif est de retourner tous les utilisateurs, classés par nom en ordre croissant.
Classe LfdEntityManagerProducer
1package ch.gobothegeek.lofidrox.repositories;
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// Produces an entityManager for persistence unit described in persistence.xml file.
13// Nothing fancy here. Just keep in mind that persistence unit name is LfdPersistenceUnit
14@ApplicationScoped
15public class LfdEntityManagerProducer implements Serializable {
16 @PersistenceUnit(name = "LfdPersistenceUnit") private EntityManagerFactory entityManagerFactory;
17
18 // return an EntityManager use to access data
19 @Produces
20 @TransactionScoped
21 public EntityManager create() {
22 return this.entityManagerFactory.createEntityManager();
23 }
24
25 // close current entity manager
26 public void close(@Disposes EntityManager em)
27 {
28 if (em.isOpen()) { em.close(); }
29 }
30
31 // getter/setter for EntityManagerFactory
32 public EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; }
33 public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; }
34}
Cette classe permet la mise à disposition d’un EntityManager (requis par JPA) permettant l’accès à la base de données. La gestion de cet EntityManager est réalisée au travers d’une factory décrite dans le fichier persistence.xml
, sous le nom LfdPersistenceUnit.
Conclusion
L’accès aux données proposé par Apache Deltaspike au travers des repositories est simple et efficace, surtout en rapport de ce que propose Hibernate.
Code source
Comme annoncé dans l’article Fuyez GitHub, le code n’est plus disponible sur GitHub. L’intégralité du code est disponible sur mon CodeBerg: https://codeberg.org/GoboTheGeek/LoFiDroX