Search code examples
javahibernatelazy-initializationspring-webflow-2

Hibernate session available for queries but lazyInit not working in flow


I am using hibernate 4.3 and SWF 2.4 and encountered a strange behavior in a particular case which leads to a LazyInitializedException.

Here is the configuration of the flow :

<persistence-context />

<on-start>
    <!-- attach enterprise to session if not provided -->
    <evaluate expression="enterpriseInput ?: enterpriseDaoImpl.merge(externalContext.sessionMap.workingEnterprise)" result="flowScope.enterprise"/>
</on-start>

<view-state id="start" view="filiation/list">
    <transition to="showBoardOrGoToList" on="save" />
</view-state>

<action-state id="showBoardOrGoToList">
    <evaluate expression="flowController.displayBoard(event)"  />
    <transition on="yes" to="boardSubflow"/>
    <transition on="no" to="something" />
</action-state>

<subflow-state id="boardSubflow" subflow="board">
    <on-entry>
        <evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
    </on-entry>
    <input name="admins" value="flowScope.admins"/>

    <output name="enterprise" value="flowScope.enterprise"/>

    <transition on="save" to="end">
        <evaluate expression="enterpriseDaoImpl.merge(enterprise)" result="flowScope.enterprise"/>
    </transition>
    <transition on="cancel" to="cancel"/>
</subflow-state>

...

When the flow reaches the following line :

<evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />

It will trigger a LazyInitializedException, the method initAdmins() basically does a Hibernate.initialize() on the elements of a list.

for (Administrator administrator : enterprise.getAdministrators()) {
    Hibernate.initialize(administrator);
}

If just before that line a method on a DAO is called it will perform the DB query without issue, so this means that the hibernate session is available at this point but the lazyInit is not working.

BUT, if i call initAdmins() in the <on-start> tag of the flow, it works...

THe flow config that works :

<persistence-context />

<on-start>
    <!-- attach enterprise to session if not provided -->
    <evaluate expression="enterpriseInput ?: enterpriseDaoImpl.merge(externalContext.sessionMap.workingEnterprise)" result="flowScope.enterprise"/>
    <evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
</on-start>

<view-state id="start" view="filiation/list">
    <transition to="showBoardOrGoToList" on="save" />
</view-state>

<action-state id="showBoardOrGoToList">
    <evaluate expression="flowController.displayBoard(event)"  />
    <transition on="yes" to="boardSubflow"/>
    <transition on="no" to="something" />
</action-state>

<subflow-state id="boardSubflow" subflow="board">

    <input name="admins" value="flowScope.admins"/>

    <output name="enterprise" value="flowScope.enterprise"/>

    <transition on="save" to="end">
        <evaluate expression="enterpriseDaoImpl.merge(enterprise)" result="flowScope.enterprise"/>
    </transition>
    <transition on="cancel" to="cancel"/>
</subflow-state>

...

The problem is that i want to initialize the admins only when required as this is a costly operation.

There is very little information on the mechanism of the persistence-context so i couldn't find an explanation on this.

For me it really looks like a bug.

Can someone highlight me or explain why the lazyInit is broken outside of the on-start tag? Thx


Solution

  • Not sure what is causing the issue. I've experienced this before and I agree I think it is a bug as well however I don't think it's worth the effort to troubleshoot because we'd have to step into the WebFlow code (done this a few times... not fun takes hours) and I've always found a simple and unintrusive work around (5 minutes or less).

    Try to initialize the admins in the following transition

    <transition on="yes" to="boardSubflow"/> 
    

    see if it works now. (would be the same effect as on-start inside the subflow in terms of your goals of efficiency).

    <transition on="yes" to="boardSubflow">
            <evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
    </transition>
    

    If that doesn't work try to create an action-state who's sole purpose is to init admins. Place this action-state in the middle the transition -> subflow call. Make sure to evaluate the expression in either the on-start or on-exit of this action-state.

    <action-state id="initAdminsActionState">
          <transition to="boardSubflow"/> 
          <on-exit>
              <evaluate expression="boardFlowController.initAdmins(enterprise)" result="flowScope.admins" />
          </on-exit>
    </action-state>