The Spring MVC application needs to execute an intensive computation job which takes several minutes. The client wants to run this job in asynchronous way. But after I enable the @Async annotation on the method (see code 1) and get the error (see code 2). My web.xml and web-appliaiton-context.xml are also presented below. I tried to find a workaround to fix this, but failed. Please help.
code 1:
@Service
public class AsyncServiceImpl implements AsyncServiceInt{
protected int pertArrSize = 1000;
@Autowired
protected TblvDao tblvDao1 = null;
@Override
@Async
public void startSlowProcess(Integer scenarioId) throws SecurityException, IllegalArgumentException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
batchUpdateSummaryPertSim(scenarioId);
}
public void batchUpdateSummaryPertSim(Integer scenarioId) throws SecurityException, IllegalArgumentException, IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException
{
double[] summaryArry = new double[pertArrSize];
summaryArry = summaryPertSim_env(scenarioId, summaryArry);
}
@Transactional
private double[] summaryPertSim_env(Integer scenarioId,
double[] summaryArry) throws IOException, ClassNotFoundException, SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
ScenarioTblv scenario = tblvDao1.getScenarioTblv(scenarioId);
TblvResultSaved savedResult = scenario.getTblvResultSaved();
TblvScenarioCategory4 tblvScenarioCategory4 = new TblvScenarioCategory4();
List<TblvScenarioCategory4> tblvScenarioCategory4List = scenario
.getTblvScenarioCategory4List();
Double min = 0.0, max = 0.0, ml = 0.0;
Integer conf = 4;
if (savedResult.getEnvDist() != null) {
byte[] st = (byte[]) savedResult.getEnvDist();
ByteArrayInputStream baip = new ByteArrayInputStream(st);
ObjectInputStream ois = new ObjectInputStream(baip);
summaryArry = (double[]) ois.readObject();
} else {
for (int i = 0; i < tblvScenarioCategory4List.size(); i++) {
tblvScenarioCategory4 = tblvScenarioCategory4List.get(i);
vTblvScenarioEffectChoose v = tblvDao1
.getvTblvScenarioEffectChooseById(tblvScenarioCategory4
.getId());
if (v.getC1id() == 1) {
tblvScenarioCategory4.setImpactYearlyUnitsSim(pertArrSize);
min = tblvScenarioCategory4
.getTblvEffectScenarioImpactUnitValue()
.getLowValue();
max = tblvScenarioCategory4
.getTblvEffectScenarioImpactUnitValue()
.getHighValue();
ml = tblvScenarioCategory4
.getTblvEffectScenarioImpactUnitValue()
.getMostValue();
conf = tblvScenarioCategory4
.getTblvEffectScenarioImpactUnitValue().getConf();
if ((conf == null) || (conf == 0))
conf = 4;
double[] MOPertArr;
MOPertArr = getPertArry(min, max, ml, conf, pertArrSize);
double[] benefitResult = new double[pertArrSize];
for (int j = 0; j < pertArrSize; j++) {
tblvScenarioCategory4.setSimIndex(j);
tblvScenarioCategory4
.getTblvEffectScenarioImpactUnitValue()
.setHighValue(MOPertArr[j]);
benefitResult[j] = tblvScenarioCategory4.getHighPvSum();
summaryArry[j] = summaryArry[j] + benefitResult[j];
}
tblvScenarioCategory4
.getTblvEffectScenarioImpactUnitValue()
.setHighValue(max);
}
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(summaryArry);
byte[] summaryArryAsBytes = baos.toByteArray();
savedResult.setEnvDist(summaryArryAsBytes);
tblvDao1.saveTblvResultSaved(savedResult);
}
return summaryArry;
}
code 2:
10:26:06,233 ERROR org.hibernate.LazyInitializationException:42 - failed to lazily initialize a collection of role: com.pb.prism.model.db.ScenarioTblv.tblvScenarioCategory4List, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.pb.prism.model.db.ScenarioTblv.tblvScenarioCategory4List, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
at com.pb.prism.util.AsyncServiceImpl.summaryPertSim_env(AsyncServiceImpl.java:446)
at com.pb.prism.util.AsyncServiceImpl.batchUpdateSummaryPertSim(AsyncServiceImpl.java:41)
at com.pb.prism.util.AsyncServiceImpl.startSlowProcess(AsyncServiceImpl.java:34)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:80)
at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>com.pb.prism.listener.MTAServletContextListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/web-application-context.xml</param-value>
</context-param>
<filter>
<filter-name>openEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Enables Spring Security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>encoding-filter</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>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Handles all requests into the application -->
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value />
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
web-appliation-context.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:task="http://www.springframework.org/schema/task"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.pb.prism" />
<!-- Imports the configurations of the different infrastructure systems of the application -->
<import resource="data-access-context.xml" />
<import resource="security-context.xml" />
<import resource="webmvc-context.xml" />
<bean id="applicationContextProvider" class="com.pb.prism.context.ApplicationContextProvider"></bean>
<!-- Configure the multipart resolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="2000000"/>
</bean>
<task:annotation-driven executor="myExecutor" />
<task:executor id="myExecutor" pool-size="5"/>
</beans>
The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call). (as explained in Spring @Transaction method call by the method within the same class, does not work? )
A possible solution is to extract the transactional code from the service, and put it in a separate class. This way, the call to the transactional method can be intercepted, and a transaction is available.
Eg.
@Service
public class AsyncServiceImpl implements AsyncServiceInt{
@Autowired private SlowProcess slowProcess;
@Override
@Async
public void startSlowProcess(Integer scenarioId) {
slowProcess.execute(param);
}
..
public class SlowProcess {
@Transactional
public double[] execute() { .. }
}