[JAVA] Exécution asynchrone des requêtes examinée dans Spring Boot 1.5.9

Aperçu

J'ai appris qu'il est possible d'exécuter des requêtes de référentiel de manière asynchrone en utilisant le traitement asynchrone de Spring, j'ai donc étudié comment l'implémenter. Cet article résume une implémentation simple des requêtes asynchrones et les résultats de leur opération.

environnement

référence

Préparation côté base de données

Créez une vue dont la recherche est longue pour faciliter la vérification de l'opération.

CREATE OR REPLACE VIEW async_test_view (
      id
    , sleep
    , create_at ) AS
SELECT MD5(UUID()) AS id
    , SLEEP(10) AS sleep
    , NOW() AS create_at
;

La recherche dans cette vue prendra environ 10 secondes pour renvoyer les résultats.

> select * from pseudo_delay_view;
+----------------------------------+-------+---------------------+
| id                               | sleep | create_at           |
+----------------------------------+-------+---------------------+
| da863db6ff1b064ebff03f00efdd224b |     0 | 2017-12-23 17:27:08 |
+----------------------------------+-------+---------------------+
1 row in set (10.00 sec)

Implémentation côté Spring Boot

Activer le traitement asynchrone et configurer le pool de threads

  1. Ajoutez l'annotation EnableAsync pour activer le traitement asynchrone.
  2. Bien qu'il ne soit pas nécessaire d'activer le traitement asynchrone, cet exemple l'implémente pour utiliser un pool de threads.
  3. Spécifiez la taille de la file d'attente des tâches. Un thread inactif traite les tâches stockées dans la file d'attente. La valeur par défaut de ThreadPoolTaskExecutor est Integer.MAX_VALUE.
  4. Spécifiez la taille du pool avec CorePoolSize. MaxPoolSize est le nombre maximum.
  5. Spécifiez la durée de vie des threads inactifs au-dessus de CorePoolSize. La valeur par défaut de ThreadPoolTaskExecutor est de 60 secondes.
@SpringBootApplication
// 1
@EnableAsync
public class DemoGradleApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoGradleApplication.class, args);
  }

  // 2
  @Bean
  public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

    // 3
    executor.setQueueCapacity(2);
    // 4
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(3);
    // 5
    executor.setKeepAliveSeconds(10);

    executor.afterPropertiesSet();

    return executor;
  }

}

Lorsque la limite de taille du pool est atteinte

Si le nombre de demandes dépasse MaxPoolSize + QueueCapacity, une exception appelée RejectedExecutionException sera levée par défaut.

Si vous souhaitez exécuter un traitement arbitraire pour le traitement du rejet lorsque la demande atteint la limite supérieure, transmettez la classe qui implémente l'interface RejectedExecutionHandler à ThreadPoolTaskExecutor comme indiqué ci-dessous. S'il n'est pas spécifié, la valeur par défaut est ThreadPoolExecutor # AbortPolicy. (Lève une exception RejectedExecutionException)

executor.setRejectedExecutionHandler((r,e) -> {
  //Implémentez tout processus que vous souhaitez exécuter
  throw new RejectedExecutionException("Task:" + r.toString() + " rejected from " + e.toString());
});

entité

Une implémentation de l'entité qui correspond à la vue. Il n'y a pas de notes spéciales.

@Entity
@Table(name="pseudo_delay_view")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PseudoDelay implements Serializable {

    private static final long serialVersionUID = -9169553858944816379L;

    @Id
    private String id;
    @Column(name="sleep", nullable=false)
    private Integer sleep;
    @Column(name="create_at", nullable=false)
    private LocalDateTime createAt;

}

Dépôt

Une implémentation du référentiel qui émet la requête. Implémente pour exécuter des requêtes de manière asynchrone. Tout ce que vous avez à faire est d'ajouter l'annotation Async à la méthode.

  1. Ajoutez l'annotation Async à la méthode qui exécute la requête de manière asynchrone. Utilisez également CompletableFuture comme type de retour.
  2. Il s'agit d'une méthode synchrone de comparaison.
public interface PseudoDelayRepository extends JpaRepository<PseudoDelay, String> {

    // 1
    @Async
    @Query("SELECT p FROM PseudoDelay AS p")
    CompletableFuture<PseudoDelay> findAsync();

    // 2
    @Query("SELECT p FROM PseudoDelay AS p")
    PseudoDelay findSync();

}

Dans cet exemple, CompletableFuture est utilisé, mais en plus de cela, Future et Spring ListenableFuture peuvent être utilisés. Pour plus de détails, voir Spring Data JPA - Documentation de référence - 3.4.7. Résultats de la requête Async Dans peut être confirmé.

Vérifiez si la requête s'exécute de manière asynchrone

La mise en œuvre de la classe de service pour la confirmation est la suivante.

  1. Il s'agit d'une méthode pour vérifier le fonctionnement des requêtes asynchrones.
  2. Une méthode pour vérifier les requêtes synchrones à des fins de comparaison.
@Service
@Slf4j
public class AsyncTestServiceImpl implements AsyncTestService {

  private PseudoDelayRepository repository;

  public AsyncTestServiceImpl(PseudoDelayRepository repository) {
    this.repository = repository;
  }

  // 1
  @Transactional(readOnly = true)
  @Override
  public PseudoDelay async() {
    log.debug("start async");

    CompletableFuture<PseudoDelay> future = repository.findAsync();

    //Faites quelque chose lors de l'exécution de la requête de manière asynchrone
    log.debug("execute somethings");

    PseudoDelay result = null;
    try {
      //Recevoir le résultat de l'exécution de la requête
      result = future.thenApply(res -> {
        log.debug("async result : {}", res);
        return res;
      })
      .get();

      } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
      }
    }

    log.debug("end async");
    return result;
  }

  // 2
  @Transactional(readOnly = true)
  @Override
  public PseudoDelay sync() {
    log.debug("start sync");

    PseudoDelay result = repository.findSync();
    log.debug("sync result : {}", result);

    log.debug("end sync");
    return result;
  }

}

Confirmation de l'opération de requête asynchrone

Vous pouvez voir que d'autres traitements ("*** exécuter quelque chose ***") sont exécutés immédiatement après l'émission de la requête.

2017-12-23 19:55:36.194 DEBUG 5304 --- [nio-9000-exec-4] : start async
2017-12-23 19:55:36.195 DEBUG 5304 --- [nio-9000-exec-4] : *** execute somethings ***
2017-12-23 19:55:46.198 DEBUG 5304 --- [ taskExecutor-2] : async result : PseudoDelay(id=9904388341a9d8dbdfb230fb5b675224, sleep=0, createAt=2017-12-23T19:55:36)
2017-12-23 19:55:46.199 DEBUG 5304 --- [nio-9000-exec-4] : end async

Vérification du fonctionnement des requêtes synchrones

Vous pouvez voir qu'il est bloqué jusqu'à ce que le résultat de la requête soit renvoyé.

2017-12-23 19:57:49.465 DEBUG 5304 --- [nio-9000-exec-8] : start sync
2017-12-23 19:57:59.467 DEBUG 5304 --- [nio-9000-exec-8] : sync result : PseudoDelay(id=3a19a242c0207cd9ddad551ec2ccae66, sleep=0, createAt=2017-12-23T19:57:49)
2017-12-23 19:57:59.467 DEBUG 5304 --- [nio-9000-exec-8] : end sync

Spécifiez un délai

Vous pouvez spécifier le délai. S'il ne se termine pas dans le délai spécifié, une exception TimeoutException sera levée.

result = future.get(5, TimeUnit.SECONDS);

À propos du délai d'expiration de la transaction

Nous avons défini un délai d'expiration pour la transaction et examiné ce qui se passerait si une requête qui prenait plus de temps que ce délai était exécutée de manière asynchrone. Cette fois, j'ai défini le délai d'expiration de la transaction à 5 secondes et j'ai exécuté une requête qui a pris 10 secondes à la fois de manière asynchrone et synchrone comme dans l'exemple ci-dessus.

@Transactional(readOnly = true, timeout = 5)

** Pour les requêtes asynchrones **

Lorsque le délai d'attente a été dépassé, aucune exception n'a été déclenchée et le résultat attendu n'a pas été atteint. Je ne connais pas la cause, mais il semble que si vous l'exécutez dans un autre thread de la transaction, ce sera hors de la gestion des transactions. J'ai vérifié s'il y a une description dans le document officiel, mais je ne l'ai pas complètement vérifié. Pour le moment, il y avait Spring @Async et gestion des transactions dans un tel article.

** Pour les requêtes synchrones **

Je pense que cela dépend de l'implémentation du pilote JDBC, mais dans le cas de MySQL, l'erreur suivante se produira.

2017-12-23 20:17:18.297 ERROR 2260 --- [nio-9000-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : Statement cancelled due to timeout or client request
2017-12-23 20:17:18.319 ERROR 2260 --- [nio-9000-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/app] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not extract ResultSet; nested exception is org.hibernate.exception.GenericJDBCException: could not extract ResultSet] with root cause

com.mysql.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2827) ~[mysql-connector-java-5.1.44.jar:5.1.44]

/...réduction

Recommended Posts

Exécution asynchrone des requêtes examinée dans Spring Boot 1.5.9
Traitement asynchrone avec exécution régulière dans Spring Boot
Résumé de ce que j'ai appris sur Spring Boot
Résumé de ce que j'ai appris dans Spring Batch
05. J'ai essayé de supprimer la source de Spring Boot
J'ai essayé de réduire la capacité de Spring Boot
Comment utiliser CommandLineRunner dans Spring Batch of Spring Boot
Spécifiez spring.profiles.active via context-param dans web.xml dans Spring Boot
Définir le paramètre contextuel dans Spring Boot
Multi-projets Spring Boot 2 avec Gradle
Changements majeurs dans Spring Boot 1.5
NoHttpResponseException dans Spring Boot + WireMock
Accélérez les tests des validateurs qui nécessitent DI dans Spring Boot
Exécution du traitement initial à l'aide de Spring Boot Command Line Runner
Spring Boot Hello World dans Eclipse
Développement d'applications Spring Boot dans Eclipse
Mémorandum lorsque Spring Boot 1.5.10 → Spring Boot 2.0.0
Obtenez une instance proxy du composant lui-même dans Spring Boot
Écrire du code de test avec Spring Boot
J'ai participé au JJUG CCC 2019 Spring
Sortie de message (Spring boot)
Implémentation du traitement asynchrone dans Tomcat
Ce que j'ai fait lors de la migration de la série Spring Boot 1.4 vers la série 2.0
Implémenter l'API REST avec Spring Boot
Qu'est-ce que @Autowired dans Spring Boot?
Implémenter l'application Spring Boot dans Gradle
Ce que j'ai fait lors de la migration de la série Spring Boot 1.5 vers la série 2.0
J'ai essayé GraphQL avec Spring Boot
Je veux contrôler le message d'erreur par défaut de Spring Boot
J'ai essayé Flyway avec Spring Boot
Je veux connaître la méthode du contrôleur où l'exception a été levée dans le ExceptionHandler de Spring Boot
Comment utiliser Thymeleaf avec Spring Boot
Erreur inconnue dans la ligne 1 de pom.xml lors de l'utilisation de Spring Boot dans Eclipse
[Spring Boot] J'ai étudié comment implémenter le post-traitement de la demande reçue.
Étapes pour rendre Spring Boot capable de faire référence à la valeur dans le fichier de propriétés
Je suis resté coincé en utilisant un cas de serpent pour le nom de variable dans Spring Boot
Mon mémorandum que je veux faire ValidationMessages.properties UTF8 dans Spring Boot
Lancer un (ancien) projet Spring Boot avec IntelliJ
Créer une image Spring Boot + Docker avec Gradle
Priorité d'accès aux fichiers statiques dans Spring Boot
Implémentation du traitement asynchrone compatible multi-tenant dans Tomcat
Sortie du journal Spring Boot au format json
Mémorandum de téléchargement de fichier local avec Spring Boot
Créer un projet Java Spring Boot avec IntelliJ
Desserrer la vérification de la syntaxe de Thymeleaf dans Spring Boot
[Entraine toi! ] Affichez Hello World avec Spring Boot
Mémorandum WebMvcConfigurer de Spring Boot 2.0 (printemps 5)
Utiliser la méthode de requête DynamoDB avec Spring Boot
J'ai essayé l'initialisation paresseuse avec Spring Boot 2.2.0
J'étais accro au @Transactional de Spring
Traitement asynchrone avec Spring Boot en utilisant @Async
DI SessionScope Bean dans le filtre Spring Boot 2
[* Java *] J'ai participé au JJUG CCC 2019 Spring
Modifier le délai d'expiration de la session dans Spring Boot
Organisez les différences de comportement de @NotBlank, @NotEmpty et @NotNull avec Spring Boot + Thymeleaf
Obtenez le chemin défini dans la classe Controller de Spring Boot sous forme de liste
J'ai essayé de cloner une application Web pleine de bugs avec Spring Boot
Comment définir des variables d'environnement dans le fichier de propriétés de l'application Spring Boot
Spécialiste de la sécurité de l'automne 2017 J'ai vérifié la fréquence des mots qui apparaissaient le matin 2