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.
1
2
3
4
5
6
7
8
9
10
| package ch.gobothegeek.lofidrox.repositories;
import org.apache.deltaspike.data.api.EntityManagerDelegate;
import org.apache.deltaspike.data.api.EntityPersistenceRepository;
import org.apache.deltaspike.data.api.criteria.CriteriaSupport;
import java.io.Serializable;
// describe a generice repository.
// Please note that this repository is NOT annotated with @Repository (otherwise it will be used for queries, leading to errors)
public 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| package ch.gobothegeek.lofidrox.repositories;
import ch.gobothegeek.lofidrox.model.entities.FileDescriptor;
import ch.gobothegeek.lofidrox.model.entities.FileDescriptorPK;
import ch.gobothegeek.lofidrox.services.FileDescriptorService;
import org.apache.deltaspike.data.api.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Optional;
// repository used to access files table
@ApplicationScoped
@Repository(forEntity = FileDescriptor.class)
public abstract class FileDescriptorRepository extends LfdRepository<FileDescriptor, FileDescriptorPK> {
private final Logger logger = LoggerFactory.getLogger(FileDescriptorRepository.class);
@Inject private FileRecipientRepository fileRecipientRepository;
@Inject private FileDescriptorService fileDescriptorService;
// return file by its name
@Transactional(Transactional.TxType.REQUIRED)
public abstract Optional<FileDescriptor> findById(Integer id);
@Transactional(Transactional.TxType.REQUIRED)
public abstract List<FileDescriptor> findByIdIn(List<Integer> ids);
// create the required file
@Transactional(Transactional.TxType.REQUIRED)
public FileDescriptor createFile(String filename, String path, String source, String type) {
FileDescriptor file;
file = new FileDescriptor(null, filename, path, source, new Date(), type);
file = this.save(file);
return file;
}
// update file path
@Transactional(Transactional.TxType.REQUIRED)
public FileDescriptor updateFilePathAndType(Integer id, String path, String type) {
Optional<FileDescriptor> file;
file = this.findById(id);
if (file.isPresent()) {
file.get().setPath(path);
file.get().setDataType(type);
this.save(file.get());
return file.get();
}
return null;
}
// delete specified file
@Transactional(Transactional.TxType.REQUIRED)
public void deleteFile(Integer id) {
Optional<FileDescriptor> file;
file = this.findById(id);
file.ifPresent(this::remove);
}
}
|
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:
1
| if (file.isPresent()) { this.remove(file); }
|
Repository FileRecipientRepository#
Ce repository permet la manipulation des entités FileRecipient.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
| package ch.gobothegeek.lofidrox.repositories;
import ch.gobothegeek.lofidrox.model.entities.FileRecipient;
import ch.gobothegeek.lofidrox.model.entities.FileRecipientPK;
import org.apache.deltaspike.data.api.Query;
import org.apache.deltaspike.data.api.QueryParam;
import org.apache.deltaspike.data.api.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
// repository used to access files recipients table
@ApplicationScoped
@Repository(forEntity = FileRecipient.class)
public abstract class FileRecipientRepository extends LfdRepository<FileRecipient, FileRecipientPK> {
private final Logger logger = LoggerFactory.getLogger(FileRecipientRepository.class);
// return recipient by its id
@Transactional(Transactional.TxType.REQUIRED)
public abstract Optional<FileRecipient> findById(Integer id);
// return recipients for a file
@Transactional(Transactional.TxType.REQUIRED)
public abstract List<FileRecipient> findAnyByFileId(Integer id);
// return recipient for a file and a user
@Transactional(Transactional.TxType.REQUIRED)
public abstract Optional<FileRecipient> findAnyByUserToAndFileId(String userTo, Integer id);
// create the required file
@Transactional(Transactional.TxType.REQUIRED)
public FileRecipient createLink(Integer fileId, String userTo, Boolean downloaded) {
FileRecipient file;
file = new FileRecipient(null, fileId, userTo, downloaded);
file = this.save(file);
return file;
}
// return list of files for a specific user
@Transactional(Transactional.TxType.REQUIRED)
@Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file f WHERE rec.userTo = :uto ORDER BY f.name")
public abstract List<FileRecipient> listFilesForUser(@QueryParam("uto") String userTo);
// delete specified file
@Transactional(Transactional.TxType.REQUIRED)
public void deleteLink(String user, Integer id) {
Optional<FileRecipient> recipient;
recipient = this.findAnyByUserToAndFileId(user, id);
recipient.ifPresent(this::remove);
}
// return file for a specific id and user
@Transactional(Transactional.TxType.REQUIRED)
@Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file file WHERE rec.userTo = :uto AND file.id = :fid")
public abstract Optional<FileRecipient> findFileByIdAndUserTo(@QueryParam("fid") Integer id, @QueryParam("uto") String userTo);
// mark file read
@Transactional(Transactional.TxType.REQUIRED)
public FileRecipient markRead(FileRecipient recipient) {
if (null != recipient) {
recipient.setDownloaded(true);
recipient = this.save(recipient);
}
return recipient;
}
}
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| package ch.gobothegeek.lofidrox.repositories;
import ch.gobothegeek.lofidrox.model.entities.User;
import ch.gobothegeek.lofidrox.model.entities.UserPK;
import org.apache.deltaspike.data.api.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
// repository used to access users table
@ApplicationScoped
@Repository(forEntity = User.class)
public abstract class UserRepository extends LfdRepository<User, UserPK> {
private final Logger logger = LoggerFactory.getLogger(UserRepository.class);
// return user if found by its username
@Transactional(Transactional.TxType.REQUIRED)
public abstract Optional<User> findByUsername(String username);
// with username and encoded password, returns the required user (if found)
@Transactional(Transactional.TxType.REQUIRED)
public abstract Optional<User> findByUsernameAndPwd(String username, String pwd);
// create the required user, with encrpyted password
@Transactional(Transactional.TxType.REQUIRED)
public User createUser(String username, String pwd) {
User user;
user = new User(username, pwd);
user = this.save(user);
return user;
}
// return list of users
@Transactional(Transactional.TxType.REQUIRED)
public abstract List<User> findAllOrderByUsernameAsc();
}
|
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#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| package ch.gobothegeek.lofidrox.repositories;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.transaction.TransactionScoped;
import java.io.Serializable;
// Produces an entityManager for persistence unit described in persistence.xml file.
// Nothing fancy here. Just keep in mind that persistence unit name is LfdPersistenceUnit
@ApplicationScoped
public class LfdEntityManagerProducer implements Serializable {
@PersistenceUnit(name = "LfdPersistenceUnit") private EntityManagerFactory entityManagerFactory;
// return an EntityManager use to access data
@Produces
@TransactionScoped
public EntityManager create() {
return this.entityManagerFactory.createEntityManager();
}
// close current entity manager
public void close(@Disposes EntityManager em)
{
if (em.isOpen()) { em.close(); }
}
// getter/setter for EntityManagerFactory
public EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; }
public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; }
}
|
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