13 Replies Latest reply on Mar 10, 2008 5:08 AM by darthmaul

    Fetching Data Lazily On A New Page

    darthmaul

      I am using JPA/Hibernate and Hibernate Search within my Seam application.  I have a Widget entity that lazily loads its pricing history.  The use case is pretty typical:




      • Search for widgets on a form with a set of criteria.

      • Display all widget search results in a table below the search form.  The results include all the items found in the Widget table itself including the widget number, which is a link to another page.

      • When you click on the widget number, you come to a widget details page that fetches all the 1:M relationships widgets have with other entities to get information like pricing history.  



      Note: Because I am using the RichFaces dataScroller with server-side paging, I have to use my own fancy data model and cannot use the standard Seam annotations like @DataModel and @DataModelSection.


      The way I am doing my own version of @DataModelSection is to do the following:


      <h:commandLink value="#{widget.number}" action="#{searchAction.setSelectedWidget(widget)}" />


      where widget is the iteration variable.


      Then through pages.xml, I navigate to the widget details page.  All of that works fine until I get this:



      org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myapp.persistence.Widget.pricingHistory, no session or session was closed
           at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
           at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
           at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:97)
           at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225)
           at javax.faces.model.ListDataModel.isRowAvailable(ListDataModel.java:99)
           at javax.faces.model.ListDataModel.setRowIndex(ListDataModel.java:174)
           at javax.faces.model.ListDataModel.setWrappedData(ListDataModel.java:209)
           at javax.faces.model.ListDataModel.<init>(ListDataModel.java:68)
           at org.ajax4jsf.component.SequenceDataAdaptor.getDataModel(SequenceDataAdaptor.java:79)
           at org.ajax4jsf.component.SequenceDataAdaptor.createDataModel(SequenceDataAdaptor.java:64)
           at org.ajax4jsf.component.UIDataAdaptor.getExtendedDataModel(UIDataAdaptor.java:592)
           at org.ajax4jsf.component.UIDataAdaptor.getRowIndex(UIDataAdaptor.java:299)
           at jrockit.reflect.VirtualNativeMethodInvoker.invoke(Ljava.lang.Object;[Ljava.lang.Object;)Ljava.lang.Object;(Unknown Source)
           at java.lang.reflect.Method.invoke(Ljava.lang.Object;[Ljava.lang.Object;I)Ljava.lang.Object;(Unknown Source)
           at com.sun.facelets.util.DevTools.writeAttributes(DevTools.java:240)
           at com.sun.facelets.util.DevTools.writeStart(DevTools.java:284)
           at com.sun.facelets.util.DevTools.writeComponent(DevTools.java:189)
           at com.sun.facelets.util.DevTools.writeComponent(DevTools.java:207)
           at com.sun.facelets.util.DevTools.writeComponent(DevTools.java:207)
           at com.sun.facelets.util.DevTools.writeComponent(DevTools.java:207)
           at com.sun.facelets.util.DevTools.writeComponent(DevTools.java:207)
           at com.sun.facelets.util.DevTools.debugHtml(DevTools.java:133)



      Now I understand that the Widget has been detatched from the JPA EntityManager by this time.  But when I click that link and come to the details page, I would like the entity to be smart enough to open another session behind the scenes and grab the information it now needs.


      Is such a thing possible?  How do I accomplish what I am trying to do?


      Any insight is appreciated.


      Thanks.

        • 1. Re: Fetching Data Lazily On A New Page
          keithnaas

          Are you using a Seam Managed Persistence Context (SMPC)?  If not, then see  this and this for details on how to get this setup.


          A really, really lazy approach is to load the data before you render the list page.  Since that data is already loaded, no LIEs.  I'm not too proud to admit I have actually done this in the past :)


          Good luck,
          Keith

          • 2. Re: Fetching Data Lazily On A New Page
            darthmaul

            I forgot to mention that I am in fact using a Seam-managed persistence contact.  Here is my components.xml:



            <persistence:entity-manager-factory name="emf" persistence-unit-name="myWebapp" />
            <persistence:managed-persistence-context name="em" auto-create="true" entity-manager-factory="#{emf}" />
            <transaction:entity-transaction entity-manager="#{em}"/>



            That's what puzzles me even more.


            As for your realllllyyyyy lazy loading approach, believe me.  I would not be above going eager if circumstances dictate.  Still, I have a feeling this is a common enough problem to solve reasonably easily, so why not give that a shot?


            So is there some configuration I can apply so that the widget knows to go fetch the rest of itself?  Or how do I use the entityManager to connect the detached instance to the database and get the rest of itself?


            Thanks.

            • 3. Re: Fetching Data Lazily On A New Page
              darthmaul

              I forgot to mention a few other things:




              • The action class that manages the search for widgets and the display of the selected widget is in conversation scope.

              • The action class is injected with a class that abstracts the search capability.

              • The only entity manager I use in this conversation is the FullTextEntityManager associated with Hibernate Search.  The FTEM is found in that search class injected into the action class.  Here are the properties in persistence.xml:




              <properties>
                       <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>
                       <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
                       <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.WeblogicTransactionManagerLookup"/>
                       <property name="hibernate.search.default.directory_provider" value="org.hibernate.search.store.FSDirectoryProvider"/>
                       <property name="hibernate.search.default.indexBase" value="C:/search/indexes"/>
                       <property name="hibernate.search.analyzer" value="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
              </properties>




              I read here that my setup in components.xml where I specify this:

              <persistence:managed-persistence-context name="em" auto-create="true" entity-manager-factory="#{emf}" />


              should set a conversation-scoped persistence context.  Clearly, that is not the case.  Could the FullTextEntityManager be an issue here?


              Thanks for any insight.

              • 4. Re: Fetching Data Lazily On A New Page
                darthmaul

                In addition, I was curious how/where the FTEM closes the session.


                When I get back to the office, I am going to try injecting an EntityManager instance with @In and manually constructing an instance of FTEM in a method annotated with @Create--rather than have the FTEM injected directly. Since my setup should mean that my EntityManager is conversation-scoped, maybe this will help the session stay open for the duration of the conversation.


                Thanks.

                • 5. Re: Fetching Data Lazily On A New Page
                  darthmaul

                  In case this helps, here is the log for when I click the link to go to the details page:




                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.transaction.JDBCTransaction - commit
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.impl.SessionImpl - automatically flushing session
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.jdbc.JDBCContext - before transaction completion
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.impl.SessionImpl - before transaction completion
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.transaction.JDBCTransaction - re-enabling autocommit
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.transaction.JDBCTransaction - committed JDBC Connection
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.jdbc.JDBCContext - after transaction completion
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.jdbc.ConnectionManager - aggressively releasing JDBC connection
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.jdbc.ConnectionManager - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
                  06 Mar 2008 14:41:23,662: DEBUG org.hibernate.impl.SessionImpl - after transaction completion
                  06 Mar 2008 14:41:23,672: DEBUG org.hibernate.transaction.JDBCTransaction - begin
                  06 Mar 2008 14:41:23,672: DEBUG org.hibernate.jdbc.ConnectionManager - opening JDBC connection
                  06 Mar 2008 14:41:23,672: DEBUG org.hibernate.transaction.JDBCTransaction - current autocommit status: true
                  06 Mar 2008 14:41:23,672: DEBUG org.hibernate.transaction.JDBCTransaction - disabling autocommit
                  06 Mar 2008 14:41:23,672: DEBUG org.hibernate.jdbc.JDBCContext - after transaction begin
                  06 Mar 2008 14:41:23,672: DEBUG org.hibernate.impl.SessionImpl - setting flush mode to: MANUAL
                  



                  I should also mention that the Seam debug page does not have the FullTextEntityManager, or any EntityManager for that matter, in any context.


                  Again, any insight into how to control the commit of the FTEM and the closing of the session is much appreciated.


                  Thanks.

                  • 6. Re: Fetching Data Lazily On A New Page
                    gavin.king

                    Seam Managed Persistence Contexts are only held open across a conversation. If there is no conversation, you can still get an LIE.


                    If you're using Hibernate, one way to solve this problem is to reassociate the object that owns the associations with the new PC using session.lock(detachedObject, LockMode.READ). Another object is to just write a HQL query based upon the id of the parent object.

                    • 7. Re: Fetching Data Lazily On A New Page
                      darthmaul

                      See, that's the confusing thing.  I am indeed in the midst of a conversation.  The action running the show is in conversational scope.  The object that is holding onto the FTEM is in the conversation context.  And at no point in the interaction I describe is the conversation ended. 


                      I have even verified that the object holding onto the FTEM is in the conversational context through a SeamTest.


                      Perhaps some code is in order.


                      Here is the relevant part of the action:



                      @Name("searchAction")
                      @Scope(value = ScopeType.CONVERSATION)
                      public class SearchAction extends Object implements Serializable {
                      .
                      .
                      .
                         @In(create = true)
                         @Out(scope = ScopeType.CONVERSATION)
                         private Search search;


                      .
                      .
                      .
                      }


                      Now the component called search is the one that actually uses Hibernate Search to look for widgets.  It therefore has the FTEM with which to do that search.  Here is the relevant part of that class:



                      @Name("search")
                      public class SearchImpl extends Object implements Search, Serializable {
                      .
                      .
                      .
                         @In
                         private FullTextEntityManager em;
                      .
                      .
                      .
                      }



                      Finally, I just checked the Seam Debug page.  It actually seems like everything is in the conversation context as it should be.


                      Here is the FTEM:


                      Component (em) 
                      class class org.jboss.seam.persistence.ManagedPersistenceContext 
                      componentName em 
                      entityManager org.jboss.seam.persistence.FullTextEntityManagerProxy@1c29764 
                      entityManagerFactory org.jboss.seam.core.Expressions$1@1c28d46 
                      entityManagerFactoryFromJndiOrValueBinding org.hibernate.ejb.EntityManagerFactoryImpl@240acac 
                      filters [] 
                      persistenceUnitJndiName java:/em 
                      toString() ManagedPersistenceContext(java:/em) 



                      And here is the search component:



                      Component (search) 
                      class class gov.nist.srm.persistence.search.SearchImpl_$$_javassist_1 
                      component Component(srmSearch) 
                      currentPage 0 
                      description   
                      keyWords  
                      name test
                      number  
                      resultsPerPage 10 
                      totalResultCount 1 
                      toString() com.myapp.persistence.search.SearchImpl@1c4a001 
                      



                      Since everything is in the conversation context and the conversation isn't over, why is the Session closed?


                      Any insight here is appreciated.  I know I can probably apply some hack to work around this, but this seems like something I would like to solve correctly as soon as possible.


                      Thanks.

                      • 8. Re: Fetching Data Lazily On A New Page
                        darthmaul

                        Something else that may be relevant...I am using JPA (powered by Hibernate), and the transaction-type="RESOURCE_LOCAL"  Does this mean that I need to add additional configuration or that I should just manage the closing of sessions on my own?


                        Thanks.

                        • 9. Re: Fetching Data Lazily On A New Page
                          darthmaul

                          It occurred to me that the manner of transition from the search page to the details page may account for the ending of the conversation in some transparent way that isn't obvious to me.


                          Here is the link that is clicked:


                          <h:column>
                             <h:commandLink value="#{widget.number}" action="#{searchAction.setSelectedWidget(widget)}" />          
                          </h:column>



                          where widget is the iteration variable.


                          Meanwhile I have this in pages.xml:



                          <navigation from-action="#{searchAction.setSelectedWidget(widget)}">
                              <redirect view-id="/details.jspx" />
                          </navigation>



                          The link is pretty much straight JSF, so I would think the conversation would propagate because this isn't a GET request.  Still, something may be awry here.  Can you see anything in there that would end the conversation?


                          Thanks.




                          • 10. Re: Fetching Data Lazily On A New Page
                            modoc

                            Are you in the midst of a Conversation or a Long Running Conversation?


                            Just because your are using Conversation scoped components, does not mean you have Begun a Long Running Conversation (which is what you need for lazing loading data on later pages/requests).


                            The default conversation only lasts through one request cycle.


                            Where do you begin your long running conversation, if you have one?  If not, start there.



                            • 11. Re: Fetching Data Lazily On A New Page
                              matt.drees

                              I see from your stacktrace you're using facelet's debug component. It's possible this is hiding the source of your problem.


                              I had a similar situation once; my persistence context was being cleared for no apparent reason; no stacktrace, etc.  After some debugging, I found that my persistence context was closed as a result of a transaction rollback.  The rollback was initiated by Seam's RollbackInterceptor, when an exception was thrown during the rendering of the facelets debug component. 


                              That component walks the component tree and calls many of the methods the component's properties are bound to.  So if you have something like <...rendered="#{comp.rendered}"}>, Comp.isRendered() is called and the result is stored for the debug output.  However, if isRendered() throws an exception (in my case, a Seam RequiredException), the facelets debug component silently swallows the exception.  However, the RollbackInterceptor saw the exception, so the transaction is rolled back, and the persistence context is cleared (and maybe closed... can't remember for sure).


                              So, maybe something similar is happening in your case.
                              If worse comes to worse, get the source for Hibernate and put a breakpoint in SessionImpl#close() and see what's triggering it.


                              Good luck!

                              • 12. Re: Fetching Data Lazily On A New Page
                                darthmaul

                                A fair point, Devon, and it is my bad for not being more explicit about that.  I am indeed in a long-running conversation.  Or perhaps it is more precise to say that I have demarcated a long-running conversation. 


                                When the user fills out his search parameters and clicks Search, the action method to which that button is bound is annotated with @Begin(join=true).  In fact, I have the same thing set up in my pages.xml as well with <begin-conversation/>.  I have an end set up too with <s:link>, but I am not getting to that in my scenario.


                                Still, it does seem like the conversation is maintained when I come back to the same page with the results after the user submits...but ends when I am attempting to visit another page.


                                Please see my post regarding the link I use to get to the details page for my latest theory on that.


                                Thanks.

                                • 13. Re: Fetching Data Lazily On A New Page
                                  darthmaul

                                  I was thinking that something might be swallowing an exception, but I had no idea what that might be.  Thanks for making me aware that the Facelets debug component could be the culprit.


                                  I think my next step will be to just try to fetch the collections eagerly for the time being and see what happens where conversation scope taken out of the question (sort of).  Perhaps an exception gets thrown when the join is attempted that is currently getting swallowed, and fetching eagerly would expose that.


                                  Thanks for the tip.