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)
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.
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");
}
}
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é.
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.
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.