web-dev-qa-db-fra.com

@Async ne fonctionne pas dans l'API Spring avec des interfaces

Je travaille avec @Async pour stocker des données en parallèle dans la base de données avec Hibernate. Je dois le faire car avant de sauvegarder les informations dans la base de données, je dois exécuter une tâche qui prend plusieurs minutes. J'ai donc implémenté @Async.

Le problème est que @Async semble ne pas fonctionner. Veuillez trouver le code ci-dessous:

WebConfig

@Configuration
@EnableAsync
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

}

StudentServiceImpl:

@Autowired
RunSomeTaskService runSomeTaskService;

@Override
Transactional
public Response saveWithoutWaiting(StudentBO[] students, String username) throws Exception {
...
for (StudentBO student : students) {
    ....
    Future<Response> response = runSomeTaskService.doTasks(student);
    //Finish without waiting for doTasks().
}

@Override
    Transactional
    public Response saveWithWaiting(StudentBO[] students, String username) throws Exception {
    ...
    for (StudentBO student : students) {
        ....
        Future<Response> response = runSomeTaskService.doTasks(student);
        //Finish and wait for doTasks().
        response.get();
    }

RunSomeTaskService:

public interface RunSomeTaskService{
    @Async
    public Future<Response> doTasks(Student student);
}

RunSomeTaskServiceImpl:

public class RunSomeTaskServiceImpl extends CommonService implements RunSomeTaskService{

Student student;
@Override
    public Future<Response> doTasks(Student student) {
          Response response = new Response();
          this.student = student;
          //do Task
          return new AsyncResult<Response>(response);
       }
}

web.xml

<web-app xmlns="http://Java.Sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://Java.Sun.com/xml/ns/javaee 
          http://Java.Sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>Sample Spring Maven Project</display-name>

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter>
    <filter-name>jwtTokenAuthFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>jwtTokenAuthFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

spring.config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:annotation-config  />
    <context:component-scan base-package="com.app.controller" />
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <mvc:annotation-driven />

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        ...
    </bean>

    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
...
    </bean>


    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="annotatedClasses">
            <list>
                <value>//every model generated with Hibernate</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager"
        class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="persistenceExceptionTranslationPostProcessor"
        class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

    <bean id="studentService" class="com.app.services.StudentServiceImpl"></bean>
    <bean id="studentDao" class="com.app.dao.StudentDaoImpl"></bean>
    ...

    <bean id="jwtTokenAuthFilter" class="com.app.security.JWTTokenAuthFilter" />       
</beans>

Alors, pourriez-vous m'aider à comprendre pourquoi @Async ne fonctionne pas?

UPDATE: @Async fonctionne maintenant, mais je n'obtiens pas les résultats escomptés.

Pour le cas, je dois attendre le résultat (cas de la synchronisation). CompletableFuture.get () n'attend pas la réponse et j'obtiens une erreur:

Mon code:

CompletableFuture<Response> res = extractDataService.doTask(student);

L'erreur:

org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection : <unknown>
Collection contents: [[]]
        at org.hibernate.collection.internal.AbstractPersistentCollection.setCurrentSession(AbstractPersistentCollection.Java:627)
        at org.hibernate.event.internal.OnUpdateVisitor.processCollection(OnUpdateVisitor.Java:46)
        at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.Java:104)
        at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.Java:65)
        at org.hibernate.event.internal.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.Java:59)
        at org.hibernate.event.internal.AbstractVisitor.process(AbstractVisitor.Java:126)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.Java:293)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.Java:227)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.Java:92)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.Java:73)
        at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.Java:648)
        at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.Java:640)
        at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.Java:635)
        at com.app.dao.CommonDaoImpl.addOrUpdate(CommonDaoImpl.Java:28)
        at com.app.services.ExtractDataServiceImpl.doExtraction(ExtractDataServiceImpl.Java:361)
        at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:62)
        at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
        at Java.lang.reflect.Method.invoke(Method.Java:498)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.Java:302)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.Java:190)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:157)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.Java:99)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.Java:281)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.Java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:179)
        at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.Java:108)
        at org.springframework.aop.interceptor.AsyncExecutionAspectSupport$CompletableFutureDelegate$1.get(AsyncExecutionAspectSupport.Java:237)
        at Java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.Java:1590)
        at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1149)
        at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:624)
        at Java.lang.Thread.run(Thread.Java:748)

Lorsque je n'ai pas à attendre et que j'exécute plusieurs cas de manière asynchrone, je reçois:

12:17:44.040 [DEMO-4] DEBUG o.h.r.t.b.j.i.JdbcResourceLocalTransactionCoordinatorImpl - JDBC transaction marked for rollback-only (exception provided for stack trace)
Java.lang.Exception: exception just for purpose of providing stack trace
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.markRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.Java:265) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.Java:156) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.Java:38) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.Java:231) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.Java:65) [hibernate-core-5.0.6.Final.jar:5.0.6.Final]
        at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.Java:581) [spring-orm-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.Java:761) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.Java:730) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.Java:485) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.Java:291) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.Java:96) [spring-tx-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.Java:179) [spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.Java:108) [spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at org.springframework.aop.interceptor.AsyncExecutionAspectSupport$CompletableFutureDelegate$1.get(AsyncExecutionAspectSupport.Java:237) [spring-aop-4.2.4.RELEASE.jar:4.2.4.RELEASE]
        at Java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.Java:1590) [na:1.8.0_171]
        at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1149) [na:1.8.0_171]
        at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:624) [na:1.8.0_171]
        at Java.lang.Thread.run(Thread.Java:748) [na:1.8.0_171]
12:17:44.043 [DEMO-4] DEBUG o.s.o.h.HibernateTransactionManager - Initiating transaction rollback after commit exception
org.hibernate.AssertionFailure: null id in com.app.model.FieldValue entry (don't flush the Session after an exception occurs)
16
Faabass

Une manière plus sophistiquée serait d'implémenter AsyncConfigurer et de définir AsyncExecutor sur threadPoolTaskExecutor.

Exemple de code ci-dessous

@Configuration
@EnableAsync(proxyTargetClass=true) //detects @Async annotation
public class AsyncConfig implements AsyncConfigurer {

 public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // create 10 Threads at the time of initialization
        executor.setQueueCapacity(10); // queue capacity
        executor.setMaxPoolSize(25); // if queue is full, then it will create new thread and go till 25
        executor.setThreadNamePrefix("DEMO-");
        executor.initialize();//Set up the ExecutorService.
        return executor;
    }

    @Override
    public Executor getAsyncExecutor() {
        return threadPoolTaskExecutor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new YOUR_CUSTOM_EXCEPTION_HANDLER();
    }

} 

La configuration ci-dessus détectera l'annotation @Async, où qu'elle soit mentionnée.

3
Praveen Shendge

Il suffit d'utiliser:

servlet.setAsyncSupported(true);

Par exemple

public class WebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(WebConfig.class);
        ctx.setServletContext(servletContext);
        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher",
            new DispatcherServlet(ctx));
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/");
        servlet.setAsyncSupported(true); //Servlets were marked as supporting async
        // For CORS Pre Filght Request
        servlet.setInitParameter("dispatchOptionsRequest", "true");
    }
}
5
Faiz Akram

Vous pouvez faire CompletableFuture, avec ceci vous savez quand toutes vos tâches sont terminées

List<CompletableFuture<T>> futureList = new ArrayList<>();

for(Student student:studentList){

CompletableFuture<T> returnedFuture = CompletableFuture.supplyAsync(() -> doSomething(student),executor).exceptionally(e -> {
        log.error("Error occured in print something future",e);
        return 0;
    });

futureList.add(returnedFuture);
}

Completable.allOf(futureList);

Ensuite, vous pouvez créer un pipeline avec thenCompose ou thenApply (à prendre consommateur) pour avoir un contrôle complet sur le pipeline de tâches. vous pouvez fermer les deux exécuteurs lorsque vous avez terminé en toute sécurité.

CompletetableFuture.allOff javadoc pour plus d'informations

0
Ram Kumar

Il est possible que l'annotation @EnableAsync dans WebConfig.Java ne soit jamais analysée. Le fichier web.xml pointe vers le fichier spring-context.xml.

Vous pouvez modifier la définition DispatcherServlet dans le fichier web.xml pour:

<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </init-param>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            com.yourpackage.WebConfig
        </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

Et incluez toute la configuration de spring-config.xml à cette classe. 

Ou Ajoutez <task:annotation-driven> dans le fichier spring-config.xml.

Mis à jour

Actuellement, le paquet com.app.controller est analysé dans le fichier spring-config.xml. Assurez-vous que WebConfig.Java se trouve dans ce package ou dans l’un de ses sous-packages. Sinon, ajoutez le package de WebConfig à l'attribut de package de base, séparé par une virgule.

De plus, vous pouvez contrôler le pool de threads utilisé par la tâche async. Créer un bean exécuteur 

@Bean
public Executor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setThreadNamePrefix("asynctaskpool-");
    executor.initialize();
    return executor;
} 

Et dans votre méthode asynchrone, utilisez le nom du bean comme ceci 

@Async("asyncTaskExecutor")
public Future<Response> doTasks(Student student);

Cela garantira que toutes les tâches seront exécutées dans ce pool de threads. 

0
Rohit

Enfin, je le fais fonctionner ...

J'ai utilisé les exécuteurs de la manière suivante:

ExecutorService executor = Executors.newFixedThreadPool(students.size());
for (StudentBO student : students) {
    executor.submit(() -> extractDataService.doTask(student));
}

Où doTask est une fonction régulière, lorsque je n'en ai pas besoin pour travailler dans un autre thread, je l'appelle simplement comme il est. Quand j'ai besoin des threads, j'utilise le code ci-dessus.

0
Faabass