jeudi 6 avril 2023

Multiple fetch dans une requête avec jpa

Hibernate et fort probablement les autres ORM sont limité dans la capacité de ramener tout une structure d'object imbriqué.

@Entity
private class Student{
 
    @Id
    private Long studentId; 
    private String firstname;
    private String lastname;
 
    @OneToMany(mappedBy = "student")
    private List<Course> courses

    @OneToMany(mappedBy = "student")
    private List<Book> books

}


MultipleBagFetchException

Si vous tentez de lancer cette requête provenant d'un repository
 
@Query("""
  select s 
  from Student s 
  join fetch s.courses
  join fetch s.books
""")
 List<Student> findStudentWithCoursesBooks();

vous obtiendrez une errreur de type MultipleBagFetchException. Il n'est pas possible de fetcher plus qu'une entité qui va généré un produit cartésien.

Il pourrait être possible d'éviter cette erreur en changeant les list pour des set dans l'entité Student, cependant le produit cartésien se produira toujours.


Solution avec transaction

@Query("""
  select s 
  from Student s 
  join fetch s.courses
""")
 List<Student> findStudentWithCoursesBooks(); 


Dans un service

@Transactional
public List<Student> getStudent(){
     List<Student>  students = studentRepository.findStudentWithCoursesBooks();
     for(Student student: students){
          student.getBooks().size();
     } 
}

Il y aura chargement des livres, puisque l'annotation Transactional a été utilisé l'erreur LazyInitializationException ne survientdra pas. Cependant pour chaque étudiant, une requête sql pour aller chercher les Book. C'est le problème n+1 souvent mentionné dans le domaine des orm.

S'il y a que très peu de student, et que la méthode getStudent() est très peu utilisé. Cela pourrais être une solution possible. Il y a toujours possibilité d'ajouter du cache dans l'application afin de limiter les dégats

Solution avec multiple requêtes

Il est possible de combiner de multiple requete, une pour chaque fetch que vous désirez.
Le problème du n+1 est ainsi évite.
Il faut cependant que les deux retournes les même Students dans notre cas. Il faut donc ajouter une condition

@Query("""
  select distinct(s)
  from Student s 
  join fetch s.courses
  where s.studentid  < 10
""")
 List<Student> findStudentWithCourses();
 
@Query("""
  select distinct(s)
  from Student s 
  join fetch s.books
  where s.studentid  < 10
""")
 List<Student> findStudentWithBooks(List<Student> students); 
 
 
 
Dans une classe au niveau du service
 
@Service
public StudentService{

    private StudentRepository studentRepository;

    @Transactional
    public List<Students> getStudentWithCoursesBooks(){

        List<Student> students = studentRepository.findStudentWithCourses();

        return !students.isEmpty() ?
            studentRepository.findStudentWithBooks(
               minId,
               maxId
             ) :
           students;

        }

}
 
Au niveau des requêtes seul deux requetes sont exécutés .
  

Conception d'api REST

 Il y a une multitude de question quand nous commencons à convevoir un API rest. Un débat qui reviens souvent est les ressources imbriqués, parent / enfant, sous-ressource.


Hierachie

Pourquoi opter pour

/parents/{idParent}/enfants/{idEnfant}

au lieu de 

/enfants/{idEnfant}
 
La première approche peut être préférable si on veut montrer le lien hierarchique qui unit les ressources. Il faut cependant ne pas exagérer du niveau hierachique, car l'url peut rapidement devenir très long et rendre le tout moins lisible. Il est d'ailleurs rare de voir un api avec plus de 2 niveau.

Dans le deuxième cas, pour une sauvegarde, il faudrait que la ressource est le nom du parent.

Une autre approche serait

/parents/{idParent}?enfants={idEnfant}
 

Doublon

 
Rien n'empêche d'utilisé plusieurs approches, dans ce cas, on pourrait se retrouver dans le cas qu'il y a différent endpoint pour obtenir la même ressource.
 

Longueur des url

/companies/{idCompany}/departments/{idDepartment}/projects/{idProject}/employees
 
Il faut cependant ne pas exagérer du niveau hierachique, car l'url peut rapidement devenir très long et rendre le tout moins lisible. Il est d'ailleurs rare de voir un api avec plus de 2 niveau.

Changement de la relation

Avec une approche hierarchique,  s'il y a changement dans la relation dépendant si vous désirez continuer d'offrir la ressource ou non, il faudrat penser à utiliser le versionnement de votre api. Une redirection est aussi possible via en autre un api gateway

Sécurité

L'approche est une hierachie dévoile une relation entre les ressources qui pour dans certain casne pas être dévoilé.

Par exemple sur un site de rencontre

/users/{id}/pictures/

Il est possible qu'on ne veuille pas permettre à n'importe qui d'accéder à toutes les images d'une personne. Un autre niveau de sécurité peut être ajouté afin de permettre qu'a certain type d'utilisateur d'y accéder via par exemple la notion de rôle.


Il n'y a pas de bonne ou mauvaise approche, il faut juste connaitre les différentes possibilités et employé la méthode la plus adéquate pour nos besoins. N'oublier pas de bien documenter votre api par exemple avec un outil tel que spring-doc, swagger.

mercredi 5 avril 2023

Versionnement de son api rest

 Vous avez un ensemble d'api qui est utilisé par de multiple clients. De nouvelle fonctionnalités prévue vont changer les valeurs de retour et les paramètres de certaines méthode. Afin de ne pas casser l'existant, il est possible de mettre en place plusieurs versions des apis.

Nous verrons différentes approche pour mettre en place le versionnement d'un api.

Versionnement des entités, payload

Dans la première mise en place d'un api vous avez une entité  Person

Si vous devez ajouter un nouveau champs dans cette entité, l'ancienne pourrait être renommé PersonV1 et la nouvelle PersonV2. PersonV2 ayan un champ prenom


Versionnement des url

@RestController
public class PersonController {

  @GetMapping("v1/person")
  public PersonV1 personV1() {
    return new PersonV1("Collin");
  }

  @GetMapping("v2/person")
  public PersonV2 personV2() {
    return new PersonV2("Collin", "Marc");
  }
}
 
 

Versionnement par un RequestParam


@RestController public class PersonController { @GetMapping(value="/person", params="v1) public PersonV1 personV1() { return new PersonV1("Collin"); } @GetMapping(value="/person", params="v2") public PersonV2 personV2() { return new PersonV2("Collin", "Marc"); } 
}

L'appel se ferait de cette façon

http://localhost:8080/person?v2

 
 

Versionnement par l'headers de la requête

@RestController
public class PersonController {

  @GetMapping(value="/person", headers="api-version=1)
  public PersonV1 personV1() {
    return new PersonV1("Collin");
  }

  @GetMapping(value="/person", headers="api-version=2")
  public PersonV2 personV2() {
    return new PersonV2("Collin", "Marc");
  } 
 } 

Si vous utilisez un outils tel que rester, postman, il faut ajouter dans la section headers la clé

api-version

et la valeur

1


Versionnement par le media type

@RestController
public class PersonController {

  @GetMapping(value="/person", produces="application/vnd.api-v1+json")
  public PersonV1 personV1() {
    return new PersonV1("Collin");
  }

  @GetMapping(value="/person", produces="application/vnd.api-v2+json")
  public PersonV2 personV2() {
    return new PersonV2("Collin", "Marc");
  } 
 } 

Le media type doit être mis le headers avec la clé Accept.


Plusieurs approches ont été spécifiés et tous ont un moins gros acteurs du marché utilise chacune de ses approches.


GitHub utilise le média type

Microsoft utilise le heders

Twitter utilise le url 

Amazon utilise le request param



Mettre à jour la sa feature branch avec sa branche parent

Branche de fonctionnalité

Les branches de fonctionnalité sont un bon moyen de ne pas polluer la branche principale. Imaginer que l'utilisateur fait une multitude de commit pour une fonctionné x dans la branche principale entrecoupé de plusieurs autre concernant une fonctionnalité y.

Il peut devenir complexe en cas de problème de revenir en arrière ou bien simplement de suivre le développement.

Une branche de fonctionnalité permet de centralisé tous le développement autour d'une fonctionnalité avant de la mettre dans la branche principale. C'est une branche temporaire

Mettre à jour sa branche

Vous avez une branche de fonctionnalité feature/AddTaxManagement qui se base sur la branche principal par exemple develop.

De nombreuse personne envoie leur changement dans la branche develop.

Retourner dans la branche develop

>git checkout develop

 

 

Ramener les changements

>git fetch -p origin


Fusionner les changements remote en local

>git merge origin/develop


git checkout feature/AddTaxManagement

Fusionner la branche develop avec votre feature branch

>git merge develop

 

Pousser ses modifications dans la branche remote

>git push origin feature/AddTaxManagement

vendredi 10 février 2023

Fetcher les enfants d'une entité

Cet exemple fonctionne sous spring data jpa.

 

Il est possible dans une requête jpql de charger directement les relations enfants d'un objet et d'éviter le problème des requêtes N +1 lorsqu'on a besoin d'accéder à ces enfants.

Imaginons que vous avez une classe

@Entity
@Table
public class Editeur {
   @Id 
   @NotNull 
   private Long idEditeur; 
   @OneToMany(fetch = FetchType.LAZY, mappedBy = "editeur") 
   private List<Livre> livres = new ArrayList<>();
}

Dans le repository de Editeur

@Query(value=""" select e 
                    from Editeur e 
                   JOIN FETCH e.livres """ 
)
List<Editor> findByEditorWithBook();


Si vous désirez de mettre en place le paging, vous devez aussi spécifier une valeur pour le count Query.

@Query(value="""
    select e
    from Editeur e JOIN FETCH e.livres
""",
countQuery= """
    select count(e) 
    from Editor e JOIN FETCH e.licences
""")
Page<Editor> findByEditorWithLicence(Pageable pageable);


Si vous ne spécifier rien pour le countQuery,  vous auriez l'erreur

query specified join fetching, but the owner of the fetched association was not present in the select list

jeudi 15 septembre 2022

Sauvegarde via rsync sur un nas Synology DS220+

 

Introduction

L'article présentera la sauvegarde du répertoire home d'un disque en btrfs sur un NAS Synology DS220+ à l'aide de rsynch

La configuration par défaut a été utilisé pour installer la  distribution OpenSUSE Tumbleweed.Le SSD est donc formaté en BTRFS. 

Activer rsync 

Dans l'interface web de Synology, il faut s'assurer que le service rsync est activé. De plus, puisqu'on lance le rsync depuis un utilisateur externe, il faut aussi activé le rsync account.


Commande rsync

Il sera assumé qu'un utilisateur rsync_userr avec le mot de passe 1234 existe sur le NAS et qu'il est alloué d'utiliser rsync. Ce droit lui peux lui être octroyé dans l'onglet application en éditant un utilisateur.

19.23.31.115 étant l'adresse de votre NAS.

collinm étant le nom de votre utilisateur local.

rsync --progress -avr  --delete --exclude="lost+found" --exclude=".cache" /home/collinm ssh rsync_user@19.23.31.115::Backup/rsync

Cette commande synchronisera votre home local dans le répertoire rsync du disque nommé Backup. A modifier selon votre configuration.

Cette sommande vous demandera de taper votre mot de passe.

rsync sans mot de passe

La commande précédente fonctionne, cependant si vous désirez lancer cette commande automatiquement, cela n'est pas pratique de devoir entrer le mot de passe à chaque fois.

Il existe une méthode simple, mais pas des plus sécuritaire est d'utiliser sshpass.

Il s'agira d'inscrire le mot de passe qui sera passé à rsync par la suite

sshpass -p 'Double2015' rsync --progress -avr  --delete --exclude="lost+found" --exclude=".cache" /home/collinm ssh rsync_user@19.23.31.115::Backup/rsync

Cette commande pourrait être ajouté dans un script bash et lancé automatiquement par cron.

Ce n'est pas sécuritaire, mais cela pourrais être acceptable si c'est pour une utilisation domestique avec aucune autre personne que vous sur votre réseau.

Autrement je vous conseille d'opter pour la création d'une clé ssh.

mercredi 14 septembre 2022

Utiliser les touches du scanner brother

 L'article portera sur l'utilisation des touches de l'imprimante scanner HL-L2390DW. Cependant, cela fonctionne pour de nombreuses imprimantes Brother. La distribution utilisé est OpenSUSE Tumbleweed

Je me suis rendu sur la page de téléchargement pour ce périphérique.

https://support.brother.com/g/b/downloadtop.aspx?c=ca&lang=fr&prod=hll2390dw_us

J'ai pris rpm car c'est le format de package géré par ma distribution.



Ensuite j'ai téléchargé et installé


Scan-key-tool 64bit. 

 

Une fois installé, vérifié si dans les processus, brscan-skey-exe est exécuté.

ps aux | grep brother
collinm  16274  0.0  0.0 1616  2056 pts/2 Sl 10:24 0:00 /opt/brother/scanner/brscan-skey/brscan-skey-exe

Si brscan-skey-exe n'est pas trouvé, lancez le à la main.

Vous devez lancer ce programme pour chaque utilisateur qui voudra utliser le scanner.

Une fois que vous appuyer sur le bouton scan de l'imprimante, il faudra choisir l'utilisateur afin que le fichier est dans le bon répertoire.

Les fichiers sont par défaut dans le répertoire brscan.



Quand JPA rencontre ses limites, pensez à jOOQ et au MULTISET

Une seule requête, un graphe d'objets complet — ce n'est pas de la magie, c'est du SQL. La base du problème Dans le monde JPA, q...