@Async not working in Spring API rest with Interfaces

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



@Async not working in Spring API rest with Interfaces



I'm working with @Async to stored some data in parallel in the database with hibernate. I need to do that because before saving the information to the database I need to run some task that takes several minutes. So I implemented @Async.



The issue is that @Async seems to not be working. Please find the code below:



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>



So, could you please help me to understand why @Async is not working?



UPDATE: @Async is now working, but I'm not getting the expected results.



For the case, that I have to wait for the result (sync case) CompletableFuture.get() is not waiting for the response and I'm getting and error:



My code:


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



The error:


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)



When I don't have to wait and I execute several cases in Async way I get:


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)





What is this line future.get() ?
– Ervin Szilagyi
Aug 3 at 21:21


future.get()





@ErvinSzilagyi as far as I understand is to force to wait for the task to be completed... But I'm using it just in the saveWithWaiting mehtod.
– Faabass
Aug 3 at 21:33





Yes, but future is not declared in your code, ignoring the syntax error after.
– Ervin Szilagyi
Aug 3 at 21:36


future





Could you please add log with thread name in doTasks and saveWithoutWaiting method, just to check if they are called in the same thread. And are you sure the WebConfig gets scanned when you start your application?
– grape_mao
Aug 6 at 16:11


doTasks


saveWithoutWaiting


WebConfig





@Async is working perfectly fine actually. The problem is you haven't specified a specific TaskExecutor to launch your threads with. Also async request processing (<async-supported>true</async-supported>) is something then async executing a method. Those are different beasts. Also your implementation of RunSomeTaskServiceImpl is flawed as it isn't thread safe. Another question next to the DispatcherServlet do you have a ContextLoaderListener which is loading th services? As that would make your @EnableAsync pretty much useless as that lives in a different context.
– M. Deinum
Aug 10 at 11:54



@Async


TaskExecutor


<async-supported>true</async-supported>


RunSomeTaskServiceImpl


DispatcherServlet


ContextLoaderListener


@EnableAsync




5 Answers
5



More Sophisticated way would be to implement AsyncConfigurer and set the AsyncExecutor to threadPoolTaskExecutor.



Sample Code below


@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();





The above configuration will detect @Async annotation wherever mentioned





This was the issue... I created a new class in the same package than WebConfig and added the package in the base-package, but I still keep getting error for the database session... I got that the session is duplicated and things like that...
– Faabass
Aug 12 at 22:16





Is it possible for you to share the stack trace? Thanks.
– Praveen Shendge
Aug 13 at 6:20





Sure! added.. I getting hopeless about this. What I'm doing is I'm doing some process and then I'm updating those students in the database in parallel way
– Faabass
Aug 13 at 12:26





This make the app to execute the methos in async way!
– Faabass
Aug 13 at 23:30





I'm Glad that worked. Thanks.
– Praveen Shendge
Aug 14 at 5:49



Just use:


servlet.setAsyncSupported(true);



For Example


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");






I implemented that in WebConfig.java and I got and error saying that I have two ApplicationContext.. So, then I found that in the web.xml I have to add <async-supported>true</async-supported> for doing the same config that you suggested, but it's also not working... I added the definition of web.xml to the post so you can check
– Faabass
Aug 6 at 13:35





This is the error I get java.lang.IllegalStateException: No unique WebApplicationContext found: more than one DispatcherServlet registered with publishContext=true?
– Faabass
Aug 6 at 17:35



Well, finally I make it work...



I used Executors in the following way:


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



Where doTask is a regular function, that when I don't need it to work in a different thread, I just call it as it is. When I need the threads, I use the code above.





This is only a workaround. Of cause using your own executor will do the work.
– grape_mao
Aug 10 at 9:55





With this you do not need @Async.
– Rohit
Aug 10 at 11:29


@Async





The code is dangerous as you are creating thread pools without ever cleaning them up. Eventually you will run out of resources and a lot of idle threads. If you want this you have to shutdown the executor in a finally block. Also your want to limit the number of threads and don't want to create 100 threads if you only have 16 cores in your system being able to do the work.
– M. Deinum
Aug 10 at 11:49



shutdown


executor


finally





@M.Deinum and the finally will be fired AFTER all the threads are done? Or how do I make sure they are completed before triggering the finally block?
– Faabass
Aug 10 at 12:40





The shutdown closes as soon as there are no more tasks to process.
– M. Deinum
Aug 10 at 12:41


shutdown



You can do CompletableFuture , with this you know when all your tasks are complete


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);



Then you can pipeline with thenCompose or thenApply (to take consumer) to have complete control on the task pipeline. you can shutdonw executors when you are done safely.



CompletetableFuture.allOff javadoc for more info





I updated the code to use CompletableFuture.. but I'm getting errors with the hibernate session, I added the stracktrace in my post
– Faabass
Aug 13 at 12:26



There is possibility that the @EnableAsync annotation in WebConfig.java is never scanned. The web.xml points to the spring-context.xml.



You can change the DispatcherServlet definition in web.xml to:


<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>



And include all configuration from spring-config.xml to this class.



Or
Add <task:annotation-driven> in spring-config.xml.


<task:annotation-driven>



Updated



Currently, com.app.controller package is scanned in spring-config.xml. Make sure the WebConfig.java is in this package or one of it's sub-package. If not add WebConfig's package to base package attribute separated by comma.


com.app.controller



Additionally, you can control the thread pool used by async task. Create a executor bean


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



And in your async method use the bean name like this


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



This will ensure all task will be executed in this thread pool.





I added the spring-config.xml could you please take a look and share your opinion?
– Faabass
Aug 10 at 12:41





@Faabass Is your WebConfig.java in the package com.app.controller or it's subpackage? If not then it is not scanned and it's package needs to be added to base packages. Base-package attribute can have multiple comma separated packages like base-package="x.y.z.service, x.y.z.controller".
– Rohit
Aug 10 at 16:37


base-package="x.y.z.service, x.y.z.controller"





Thank! I added that to the base-package and now it is working in @Async, anyway I0m having other issues. please see my post
– Faabass
Aug 13 at 12:27





I would strongly suggest to ask a new question and mark this as answer for the original question.
– Rohit
Aug 13 at 13:00





done! stackoverflow.com/questions/51831843/…
– Faabass
Aug 13 at 23:30






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

Creating a leaderboard in HTML/JS