Spring @Async generates LazyInitializationExceptions

Alex picture Alex · Jun 24, 2013 · Viewed 7.6k times · Source

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>

Answer

davidcyp picture davidcyp · Jun 24, 2013

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() { .. }

}