lundi 30 octobre 2023

[Spring ] La gestion des transactions avec Spring @Transactional Rollback

 Introduction 

Lorsque vous travaillez avec des applications Spring, la gestion des transactions est essentielle pour garantir la cohérence et la fiabilité des opérations de base de données. L’une des questions clés dans ce contexte est de savoir comment gérer les exceptions qui se produisent pendant une transaction. Par défaut, Spring définit une règle de base pour déterminer si une transaction doit être annulée en cas d’exception. 

Cependant, il peut être nécessaire de personnaliser ces règles en fonction de vos besoins spécifiques. C’est là qu’intervient l’attribut “rollbackFor”.

Le Problème

Imaginons que vous travaillez sur une application de commerce électronique qui utilise Spring pour gérer les transactions. Lorsqu’un client passe une commande, plusieurs opérations de base de données doivent être effectuées, comme la mise à jour de l’inventaire, la création d’un nouvel enregistrement de commande et la déduction du solde du client. Si une exception se produit lors de l’une de ces opérations (par exemple, si l’inventaire est insuffisant), vous voulez que toutes les opérations soient annulées pour maintenir la cohérence des données. Cependant, par défaut, Spring ne fait un rollback que pour les exceptions non vérifiées (Unchecked Exception), ce qui peut ne pas être suffisant pour votre cas d’utilisation.

La Solution

Pour résoudre ce problème, vous pouvez utiliser l’attribut “rollbackFor” de l’annotation @Transactional de Spring. Cet attribut vous permet de spécifier les exceptions pour lesquelles un rollback doit être effectué. Par exemple, si vous voulez faire un rollback pour une exception vérifiée (Checked Exception) spécifique (disons InsufficientInventoryException), vous pouvez le faire comme suit :

@Transactional(rollbackFor = InsufficientInventoryException.class)
public void placeOrder(Order order) throws InsufficientInventoryException {
    // Code pour passer la commande
}

Dans cet exemple, si une InsufficientInventoryException est levée lors de l’exécution de la méthode placeOrder(), Spring fera un rollback de toutes les opérations de base de données effectuées dans cette méthode.

Pour tester cette fonctionnalité, vous pouvez utiliser JUnit et Mockito. Par exemple :

@Test
public void whenInsufficientInventory_thenRollback() {
    doThrow(InsufficientInventoryException.class).when(inventoryService).deductInventory(any(Order.class));
    
    assertThrows(InsufficientInventoryException.class, () -> {
        orderService.placeOrder(new Order());
    });
    
    verify(inventoryService, never()).deductInventory(any(Order.class));
}

Dans ce test, nous simulons une InsufficientInventoryException lors de l’appel à inventoryService.deductInventory(). Ensuite, nous vérifions que lorsque cette exception est levée, aucune déduction d’inventaire n’est effectuée.

Discussion

L’utilisation de l’attribut “rollbackFor” offre une grande flexibilité pour gérer les transactions dans Spring. Cependant, il est important de noter que cette approche peut conduire à une logique de gestion des transactions complexe si elle est utilisée sans discernement. Il est recommandé d’utiliser “rollbackFor” uniquement lorsque le comportement par défaut de Spring ne suffit pas.

D’autre part, il convient également de noter que “rollbackFor” ne fonctionne que lorsque la méthode annotée est appelée à partir d’un autre bean Spring. Si elle est appelée à partir du même bean (c’est-à-dire en appelant une autre méthode publique de la même classe), l’annotation @Transactional (et donc “rollbackFor”) sera ignorée.

Un autre concept clé est la propagation des transactions. La propagation détermine comment les transactions sont gérées lorsque vous avez plusieurs méthodes @Transactional imbriquées. Par exemple, si vous avez une méthode A qui appelle une méthode B, et que les deux sont annotées avec @Transactional, comment les transactions sont-elles gérées ? C’est là qu’intervient la propagation.

Par défaut, la propagation est définie sur Propagation.REQUIRED, ce qui signifie que si une transaction est déjà en cours lorsqu’une méthode @Transactional est appelée, cette transaction existante sera utilisée au lieu d’en créer une nouvelle. Cependant, vous pouvez modifier ce comportement en utilisant l’attribut propagation de @Transactional. Par exemple, si vous définissez propagation = Propagation.REQUIRES_NEW, une nouvelle transaction sera toujours créée, même s’il en existe déjà une.

Enfin et surtout, il convient également d’évoquer le rôle des proxies dans la gestion des transactions avec Spring. Lorsque vous utilisez l’annotation @Transactional, Spring crée un proxy autour de l’objet cible. Ce proxy est responsable de la gestion des transactions. Lorsqu’une méthode annotée @Transactional est appelée, le proxy démarre une transaction avant que la méthode ne soit exécutée. Si la méthode se termine normalement, la transaction est validée (commit). Si une exception est levée, la transaction est annulée (rollback). Cependant, lorsque vous appelez une autre méthode publique de la même classe (c’est-à-dire une méthode interne), le proxy est contourné et l’annotation @Transactional est ignorée. Cela signifie que les règles de gestion des transactions que vous avez définies avec @Transactional ne s’appliquent pas aux appels de méthodes internes.

Ces concepts sont essentiels pour comprendre et maîtriser la gestion des transactions dans Spring. Ils vous permettent de contrôler avec précision comment les transactions sont gérées et comment les exceptions affectent le déroulement de vos transactions. En fin de compte, une bonne compréhension de ces concepts vous aidera à construire des applications plus robustes et fiables.

dimanche 29 octobre 2023

[Java 8] Maîtrisez le passage à l’heure d’été et d’hiver avec Java : Un guide pratique pour éviter les pièges du DST

 

Introduction

Le passage à l’heure d’été (Daylight Saving Time, DST) et à l’heure d’hiver est une pratique courante dans de nombreux pays pour profiter au maximum de la lumière du jour pendant les mois d’été et économiser de l’énergie pendant les mois d’hiver.
Cependant, la gestion du DST dans les applications informatiques peut être un défi, en particulier pour les applications qui travaillent avec des dates et des heures.

Problème

Prenons l’exemple d’une application comme Météo France. Cette application utilise des horaires pour fournir des prévisions météorologiques précises à ses utilisateurs. Si elle ne tient pas compte du DST, les prévisions pourraient être décalées d’une heure. Cela signifie que si un utilisateur consulte l’application pour savoir s’il va pleuvoir à 15h00, l’application pourrait en réalité lui montrer la prévision pour 16h00. Si l’utilisateur se fie à cette information pour planifier ses activités, il pourrait se retrouver sous la pluie sans parapluie :) !

De plus, si l’application propose des alertes météorologiques en temps réel, ces alertes pourraient également être décalées d’une heure.
Par exemple, une alerte de tempête prévue pour 18h00 pourrait n’être déclenchée qu’à 19h00, ce qui pourrait mettre les utilisateurs en danger.

Un autre exemple serait une application de réservation de vols.
Si le DST n’est pas correctement géré, les heures de vol affichées pourraient être incorrectes, ce qui pourrait entraîner des confusions et des problèmes pour les voyageurs.

Solution

Pour gérer correctement le DST, vous pouvez utiliser la nouvelle API Date/Time introduite dans Java 8. Par exemple, la classe ZonedDateTime immuable et thread-safe a une prise en charge complète des fuseaux horaires, y compris le DST. Lorsque vous convertissez entre différentes classes de date/heure, la prise en charge du DST est automatiquement prise en compte si vous utilisez correctement l’API.

  • Fuseaux horaires et ZoneId

Le concept de fuseau horaire, inventé au Royaume-Uni, sert à lier une date et une heure à un contexte. Les définitions des différents fuseaux, qui résultent de choix politiques, sont susceptibles de changer au fil du temps. Avec l’ancienne API de manipulation des dates, la classe TimeZone était utilisée pour modéliser le concept de fuseau horaire. Désormais, la nouvelle API se base sur la classe ZoneId qui présente deux différences majeures avec TimeZone.

Tout d’abord, ZoneId est une classe immuable. De plus, les règles liées aux fuseaux sont définies au sein de la classe déléguée ZoneRules et non directement au sein de ZoneId. Un simple appel à la méthode getRules() de ZoneId permet d’obtenir les règles.

  • Offset et ZoneOffset

Un cas d’utilisation classique des fuseaux consiste à définir un offset par rapport au fuseau référence UTC/Greenwich. La classe ZoneOffset, qui hérite de ZoneId, modélise l’offset au niveau heure avec le méridien zéro de Greenwich à Londres.

Voici un exemple de code qui illustre comment le passage à l’heure d’été est géré avec ZonedDateTime :

import java.time.*;

public class Main {
    public static void main(String[] args) {
        // Créer une instance de ZonedDateTime pour une date avant le passage à l'heure d'été
        ZonedDateTime beforeDST = ZonedDateTime.of(2023, 3, 26, 1, 59, 59, 0, ZoneId.of("Europe/Paris"));

        // Ajouter une seconde pour passer à l'heure d'été
        ZonedDateTime afterDST = beforeDST.plusSeconds(1);

        // Afficher les dates et heures avant et après le passage à l'heure d'été
        System.out.println("Avant le passage à l'heure d'été : " + beforeDST);
        System.out.println("Après le passage à l'heure d'été : " + afterDST);
    }
}

Et voici un exemple similaire pour le passage à l’heure d’hiver :

import java.time.*;

public class Main {
    public static void main(String[] args) {
        // Créer une instance de ZonedDateTime pour une date avant le passage à l'heure d'hiver
        ZonedDateTime beforeDST = ZonedDateTime.of(2023, 10, 29, 2, 59, 59, 0, ZoneId.of("Europe/Paris"));

        // Ajouter une seconde pour passer à l'heure d'hiver
        ZonedDateTime afterDST = beforeDST.plusSeconds(1);

        // Afficher les dates et heures avant et après le passage à l'heure d'hiver
        System.out.println("Avant le passage à l'heure d'hiver : " + beforeDST);
        System.out.println("Après le passage à l'heure d'hiver : " + afterDST);
    }
}

Ces exemples affichent les dates et heures avant et après le passage à l’heure d’été et à l’heure d’hiver. Vous remarquerez que lorsqu’une seconde est ajoutée à l’heure juste avant le passage à l’heure d’été ou d’hiver, l’heure saute directement de 1:59:59 à 3:00:00 ou passage à l’heure d’hiver, l’heure saute de 3:00:00 à 2:00:00. C’est-à-dire qu’à 3h00 du matin, l’heure est réglée à 2h00, ce qui signifie qu’il y a une heure supplémentaire dans la journée.

Discussion

En résumé, une mauvaise gestion du DST peut entraîner des erreurs dans les applications qui travaillent avec des dates et des heures. La nouvelle API Date/Time de Java 8 facilite grandement cette tâche.

Il est important de noter que même avec ces outils, la gestion correcte du DST nécessite une attention particulière de la part des développeurs. Les règles du DST peuvent varier d’un pays à l’autre et peuvent changer au fil du temps. Par conséquent, il est essentiel de tester soigneusement votre application pour vous assurer qu’elle gère correctement ces changements.

datetime - Whats the difference between java.util.Date and Zoneddatetime? - Stack Overflow

[SQL] Optimisation des requêtes SQL : Identifier et corriger les mauvaises pratiques

Introduction

L’optimisation des requêtes SQL est un aspect crucial de la gestion des bases de données.
Cependant, certaines pratiques courantes peuvent entraver cette optimisation et entraîner des résultats inattendus.

Problème

Trois problèmes principaux peuvent survenir lors de l’écriture de requêtes SQL, illustrés par les 3 exemples suivants :

  1. Mauvaise pratique : Utilisation de fonctions sur des colonnes indexées dans les prédicats. Exemple : SELECT * FROM employees WHERE YEAR(hire_date) = 1999; 
    Pourquoi c’est une mauvaise pratique ?
    L’utilisation de la fonction YEAR() empêche l’utilisation de l’index sur la colonne hire_date, ce qui peut ralentir la requête.

  2. Mauvaise pratique : Incohérence des types de données dans les prédicats.
    Exemple : SELECT * FROM employees WHERE employee_id = '123';
    Pourquoi c’est une mauvaise pratique ?
    Si vous comparez des colonnes de types de données différents, SQL doit convertir l’un des types pour effectuer la comparaison, ce qui peut également empêcher l’utilisation d’un index.

  3. Mauvaise pratique : Dépendance à un ordre d’évaluation spécifique pour les prédicats dans une clause WHERE.
    Exemple : SELECT * FROM employees WHERE manager_id IS NOT NULL AND manager_id > 0; Pourquoi c’est une mauvaise pratique ?
    SQL n’a pas d’ordre d’évaluation défini pour les prédicats dans une clause WHERE. Par conséquent, si vous vous fiez à un certain ordre d’évaluation, vous pouvez obtenir des résultats inattendus.

Solution

Pour chaque problème identifié, voici une bonne pratique correspondante :

  1. Bonne pratique : Évitez d’utiliser des fonctions sur des colonnes indexées dans les prédicats. Exemple corrigé :
    SELECT * FROM employees WHERE hire_date BETWEEN '1999-01-01' AND '1999-12-31';
    Pourquoi c’est une bonne pratique ? 
    En évitant l’utilisation de fonctions sur des colonnes indexées, nous permettons à SQL d’utiliser l’index existant sur la colonne, ce qui peut améliorer considérablement les performances de la requête.

  2. Bonne pratique : Assurez-vous que les types de données sont cohérents lors de la comparaison de colonnes.
    Exemple corrigé :
    SELECT * FROM employees WHERE employee_id = 123;
    Pourquoi c’est une bonne pratique ? 
    En veillant à ce que les types de données soient cohérents lors de la comparaison, nous évitons que SQL ait à convertir les types, ce qui peut également améliorer les performances et l’exactitude des résultats.

  3. Bonne pratique : Ne vous fiez pas à un ordre d’évaluation spécifique pour les prédicats dans une clause WHERE.
    Exemple corrigé :
    SELECT * FROM employees WHERE manager_id > 0 AND manager_id IS NOT NULL;
    Pourquoi c’est une bonne pratique ?
    En évitant de se fier à un ordre d’évaluation spécifique pour les prédicats, nous nous assurons que notre requête renvoie toujours le résultat attendu, quel que soit l’ordre d’évaluation utilisé par SQL.

Discussion

En identifiant ces mauvaises pratiques et en appliquant les bonnes pratiques correspondantes, vous pouvez optimiser vos requêtes SQL et améliorer les performances de votre base de données. Il est important de continuer à apprendre et à se tenir au courant des meilleures pratiques en matière de SQL pour garantir l’efficacité et la précision de vos requêtes.

samedi 28 octobre 2023

[JAVA 11] Java Optional Best practices


Introduction

Java Optional est un conteneur d’objet qui peut ou non contenir une valeur non nulle. Il offre une alternative plus sûre à null, en évitant les exceptions NullPointerException.

Problème

Avant de plonger dans l’utilisation d’Optional, comprenons quel problème il promet de résoudre. Lorsque nous créons une application Java, nous utiliserons sûrement le chaînage de méthodes pour déréférencer les propriétés des objets, ce qui peut facilement nous faire tomber dans le piège de NullPointerException.

String authorZipCode = book.getAuthor().getAddress().getZipCode();

Dans le fragment de code ci-dessus, il pourrait y avoir une NullPointerException dans n’importe quelle invocation de méthode get.
Par conséquent, la classe Optional résout ce problème en enveloppant l’objet que nous voulons et en le manipulant.

L’utilisation incorrecte de Java Optional peut entraîner des erreurs et une mauvaise performance. Par exemple, déclarer des champs d’une classe ou dans un constructeur avec Optional n’est pas recommandé. De plus, l’appel à Optional.get() sans vérifier au préalable si une valeur est présente peut entraîner une exception NoSuchElementException.

Solution

Au lieu d’utiliser une annotation Java, qui n’est pas vérifiée à la compilation, Optional vous permet d’améliorer vos méthodes et d’indiquer que votre méthode peut retourner des valeurs “vides” (et non null).

Voici quelques bonnes pratiques pour utiliser Java Optional :

  1. Utilisez Optional uniquement pour les retours de méthode : Cela permet d’éviter les erreurs NullPointerException lors de l’appel à des méthodes qui peuvent retourner null.

  2. Initialisez les variables Optional avec empty() au lieu de null : Depuis Java 8, vous pouvez initialiser une variable Optional avec Optional.empty(). Par exemple :

    Optional<String> refMoe = Optional.empty();
    
  3. N’utilisez jamais Optional.get() sans vérifier au préalable si une valeur est présente : Utilisez toujours Optional.isPresent() avant d’appeler Optional.get(). Sinon, vous risquez de rencontrer une exception NoSuchElementException.

  4. Utilisez orElse(null) pour retourner null : Cette méthode doit être utilisée uniquement pour des valeurs fixes comme Collections.empty(), etc.

Discussion sur chaque point


Utilisation de isPresent

Optional<String> optional = getOptional();
if (optional.isPresent()) {
    System.out.println(optional.get());
}

ifPresent et ifPresentOrElse

optional.ifPresent(value -> System.out.println(value));
optional.ifPresentOrElse(
    value -> System.out.println(value),
    () -> System.out.println("Value not present")
);

orElse, orElseGet, orElseThrow

String value = optional.orElse("default");
value = optional.orElseGet(() -> "default");
value = optional.orElseThrow(() -> new RuntimeException("Value not present"));

Optional et les collections

// Mauvaise pratique
Optional<List<String>> optionalList = getOptionalList();

// Bonne pratique
List<String> list = getList(); // Retourne une liste vide si aucune valeur n'est présente

Éviter l’abus d’Optionals

// Mauvaise pratique
Optional<String> optional = Optional.of("value");

// Bonne pratique
String value = "value";

Éviter l’utilisation des Optionals pour les arguments de méthode et les champs

public class MyClass {
    private Optional<String> value; // Mauvaise pratique

    public void myMethod(Optional<String> value) { // Mauvaise pratique
        // ...
    }
}

map et flatMap

Optional<String> optional = getOptional();
optional.map(value -> value.toUpperCase());
optional.flatMap(value -> getAnotherOptional(value));

Éviter l’utilisation excessive de isPresent et get

// Mauvaise pratique
if (optional.isPresent()) {
    String value = optional.get();
}

// Bonne pratique
optional.ifPresent(value -> System.out.println(value));

En suivant ces bonnes pratiques, vous pouvez utiliser Java Optional efficacement et en toute sécurité.

J’espère que ces exemples vous aideront à comprendre comment utiliser efficacement Java Optional. 
😊



[Style de programmation] Réécrire les conditions avec le style "Yoda"

 Problème :
Vous testez les valeurs attendues sur la partie gauche de votre expression.
person.getName() != null && person.getName().equals("name");

Solution : 
Écrivez vos conditions avec la valeur de la variable à gauche et la valeur à tester à droite.
"name".equals(person.getName());

Cela signifie que vous n’avez pas besoin de vérifier si person.getName() est null. Cela économise un peu de frappe et est sans doute plus clair une fois que vous vous y êtes habitué et eviter des NPE.

Discussion :

La plupart des programmeurs écrivent d’abord la variable ou la condition et ensuite la valeur de test. En fait, c’est l’ordre correct pour les assertions. Dans certains langages, cette préférence de style est utilisée pour éviter une affectation accidentelle au lieu d’une comparaison d’égalité, ce qui peut entraîner une erreur logique dans le code.
Cela signifie que vous n’avez pas besoin de vérifier si person.getName() est null. Cela économise un peu de frappe et est sans doute plus clair une fois que vous vous y êtes habitué.


vendredi 13 octobre 2023

[ Java 21 ] Intro Virtual thread

 

Introduction

Les threads virtuels sont une nouvelle fonctionnalité introduite dans Java 21 qui permettent une exécution plus efficace des threads. 

Comprendre les threads virtuels

Avant l’introduction des threads virtuels, le nombre de threads que vous pouviez avoir dans une application Java était généralement limité par le nombre de threads du système d’exploitation. Ces threads, appelés “threads de plateforme”, sont gérés et planifiés par le système d’exploitation. Chaque thread de plateforme a une grande pile de threads et d’autres ressources qui sont maintenues par le système d’exploitation. Par conséquent, le nombre de threads de plateforme disponibles est limité par les ressources du système, y compris la CPU et la RAM.





Avec l’introduction des threads virtuels dans Java 21, vous pouvez maintenant avoir un nombre beaucoup plus grand de threads dans votre application. Les threads virtuels sont gérés et planifiés par la JVM, et non par le système d’exploitation. Ils ne sont pas liés à un thread du système d’exploitation spécifique, ce qui signifie qu’ils peuvent être répartis plus efficacement sur plusieurs processeurs. De plus, contrairement aux threads de plateforme, les threads virtuels ont généralement une pile d’appels peu profonde, ce qui signifie qu’ils utilisent moins de ressources du système.



Cela signifie que vous pouvez avoir un nombre de threads virtuels égal au nombre de tâches concurrentes dans votre application. Par exemple, si votre programme soumet 1 000 000 de tâches, il peut créer 1 000 000 de threads virtuels qui s’exécutent simultanément.


Cependant, il est important de noter que bien que les threads virtuels puissent améliorer les performances en permettant à un plus grand nombre de tâches de s’exécuter simultanément, ils ne sont pas destinés aux opérations intensives en CPU à long terme. Ils sont plus adaptés aux tâches qui passent la plupart du temps bloquées, souvent en attendant que les opérations d’E/S se terminent


Exemples pratiques

Création de threads virtuels

Voici un exemple de code qui illustre comment créer des threads virtuels en Java 211:

Runnable runnable = () -> System.out.println(Thread.currentThread().threadId());
// Créer des threads virtuels
Thread thread = Thread.ofVirtual().name("testVT").unstarted(runnable);
testVT.start();
// Créer des threads de plateforme virtuels
Thread testPT = Thread.ofPlatform().name("testPT").unstarted(runnable);
testPT.start();

Dans cet exemple, nous créons d’abord une tâche Runnable qui affiche l’ID du thread courant. Ensuite, nous utilisons les nouvelles API Thread.ofVirtual() et Thread.ofPlatform() pour créer respectivement un thread virtuel et un thread de plateforme.

Performance des threads de plateforme vs threads virtuels

Les threads virtuels peuvent aider à augmenter le débit des applications côté serveur car ces applications ont un grand nombre de tâches concurrentes, et ces tâches ont généralement un grand nombre d’attentes d’E/S. Par exemple, si votre programme soumet 1 000 000 de tâches, il peut créer 1 000 000 de threads virtuels qui s’exécutent simultanément

dimanche 1 octobre 2023

Spring AOP

Pour implémenter un aspect dans Spring, vous devez :

Activer Spring Aspect dans votre application.

Créer une classe d'aspect.

Définir une méthode d'aspect et indiquer à Spring quand et quoi intercepter.

Implémenter la logique de l'aspect dans la méthode d'aspect. 


Ces étapes vous permettront d'ajouter des fonctionnalités transversales à votre application, telles que la gestion des logs, la sécurité ou la gestion des transactions, sans avoir à modifier le code métier de votre application.


Pattern d'affichage du msg commence par Pool 
pour chercher les log de notre produit Pool 

chaque resource continet une opération 

statut en cours et fin de l'opération 
paramètres d'appel

retour du service