9 Replies Latest reply on Oct 17, 2006 5:45 AM by Wolfgang Knauf

    How to lazily load collections?

    Michael Litherland Newbie

      Hi,

      I have an EJB3 entity that contains a collection of other entities. The collection is both very rarely used and extremely large so I'd really rather not eagerly load it (in fact I can't, it makes the site slow to a crawl). The problem is that I don't know how to get around the LazyInitializationException when you specify the collection as lazy.

      I tried creating a method in my EBJ3 BL layer that did a entityManager.refresh(entity) and then called the entity.getCollection method, but that doesn't help.

      I've googled all over and searched here, but I can't figure out how to successfully load a lazy collection in EJB3. Removing the relationship from the entities would work, but that seems like it should be unnecessary. This is the error I want to avoid:

      Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.ati.raa.entities.TdmSites.tdmAlerts, no session or session was closed
      


      And the snippit:
      @OneToMany(mappedBy = "tdmSite", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
       private List<TdmAlerts> tdmAlerts;
      


      This has got to be simple and I'm just missing something. Can anyone help me out?

      Thanks,
      Mike


        • 1. Re: How to lazily load collections?
          Andrew Rubinger Master

          Looks like you're trying to access "tdmAlerts" after the transaction has ended, or after manuall closing the session held by the Entitymanager. This happens pretty frequently if you're sending the object to a remote client or storing to/retrieving from a session (HTTP, SFSB, etc), for instance.

          Where are you calling "refresh"?

          S,
          ALR

          • 2. Re: How to lazily load collections?
            Michael Litherland Newbie

            Hi,

            Thanks for the reply.

            I've tried two ways, most recently I'm doing this in an EJB stateless session bean:

            public List<TdmAlerts> getAlertsFromEntity(MyEntity entity) {
            entityManager.refresh(entity);
            return entity.getTdmAlerts();
            }
            


            Previously I was just using this same bean to return the entity after doing an entityManager.find and within my JSF application wrapping the List returned from the entity in a ListDataModel for display in the JSP. This worked before switching to LAZY. Also, I'm not closing the session and I can't even find in the java doc how to do such a thing if I wanted to! I've certainly hunted around for some clues on how to work within the session to bypass this problem.

            Thanks again for the help.

            Mike

            • 3. Re: How to lazily load collections?
              Andrew Rubinger Master

              Sorry; I assumed you were trying to iterate over the collection immediately after calling "refresh". Where is the code generating the error? Not in that SLSB snippit posted, but in the JSP?

              The List returned from the EntityManager will be a Hibernate Collections class with a reference to the current session. If you're just looking to get a "snapshot" of the current data in there at the time its requested, you can create a new List, put all of the items in the Entity bean's version in it, and safely return your list for use outside of the session/transaction. Assuming you're getting the error in the JSP:

              public List<TdmAlerts> getAlertsFromEntity(MyEntity entity) {
              entityManager.refresh(entity);
              List<TdmAlerts> tdmAlerts = new ArrayList<TdmAlerts>();
              tdmAlerts.addAll(entity.getTdmAlerts());
              return tdmAlerts;
              }


              ...I think should do it. Or use a TO/VO.

              Manually closing is done by something like:

              ((HibernateEntityManager)entityManager.getDelegate()).getSession().close();
              ... or similar

              S,
              ALR

              • 4. Re: How to lazily load collections?
                Michael Litherland Newbie

                Yes, of course you're right the exception occurred when JSF tried to display the data model, so essentially when the JSP was rendered.

                 public List<TdmAlerts> getAlertsFromEntity(MyEntity entity) {
                 entityManager.refresh(entity);
                 List<TdmAlerts> result = new ArrayList();
                 result.addAll(entity.getTdmAlerts());
                 return result;
                 }
                


                I changed the code to this and I get the same failure:

                16:22:55,838 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: com.ati.raa.entities.MyEntity.tdmAlerts, no session or session was closed
                org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.ati.raa.entities.MyEntity.tdmAlerts, 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.initialize(AbstractPersistentCollection.java:343)
                 at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
                 at org.hibernate.collection.PersistentBag.toArray(PersistentBag.java:254)
                 at java.util.ArrayList.addAll(ArrayList.java:473)
                 at com.ati.raa.bl.EntityLogicBean.getAlertsFromEntity(EntityLogicBean.java:144)
                


                So that refresh doesn't have the effect that I desired, namely to access that entity in the current session's context. Any other thoughts? Since the entity manager goes so far out of its way to hide the concept of sessions that it clearly depends on, it makes this hard for me to understand the right way to do this.

                Also, what is this VO/TO you speak of?

                Thanks much,
                Mike

                • 5. Re: How to lazily load collections?
                  Andrew Rubinger Master

                   

                  at org.hibernate.collection.PersistentBag.toArray(PersistentBag.java:254)
                   at java.util.ArrayList.addAll(ArrayList.java:473)
                   at com.ati.raa.bl.EntityLogicBean.getAlertsFromEntity(EntityLogicBean.java:144)


                  ...is your JSF stuff accessing your EJB directly?! You should be obtaining your EJBs via a proxy object, looked up through JNDI...please post your view code? Was expecting something like:

                  at Proxy$x.getAlertsFromEntity()


                  VO/TO = Value Object / Transfer Object. Serializable POJOs without any session-specific information (read: Hibernate) associated with them, intended for holding data for safe shipping between layers.

                  S,
                  ALR

                  • 6. Re: How to lazily load collections?
                    Michael Litherland Newbie

                    Uh oh - exclamation points, now I *know* I'm doing something wrong. :)

                    OK, without going too in depth here's the flow. I have a util class with static methods looking up the EJB3 stateless SBs:

                    Context c = new InitialContext();
                    return (EntityLogicRemote) c.lookup("MyProject/EntityLogicBean/remote");
                    


                    There's a controller class that handles the JSF actions. It looks up the remote using the method above. It then looks up the entity it needs by it's ID, that code in the bean looks like this:

                    MyEntity entity = em.find(MyEntity.class, entityId);
                    return entity;
                    


                    Of course that MyEntity is the EJB3 entity bean in question. I am, in fact directly referencing the returned object within my JSF class (which from your reply must be problematic). I do realize there are limitations in working with the object, so I've carefully coded those interactions. Initially I directly accessed that bean's getTdmAlerts method to get the TdmAlerts collection, which I wrapped in my DataModel (successfully before I made the relationship lazy). After I made it lazy and started getting exceptions I changed what I was doing. I currently am trying to pass the entity instance I get from the above method back to the session bean to refresh() it and get the TdmAlerts collection (which obviously fails with the same exception).

                    Now, where I am at 8:35 PM localtime() is that instead of passing the entity back to the session bean I just pass the site ID and look it up again where (as you'd expect) the newly returned entity can give me it's TdmAlerts collection without a problem. So problem solved in one sense, but I really want to do things the Right Way.

                    I appreciate greatly your help so far, can you give me guidance as to how I should structure my interactions between JSF and EJB3? When I decided to employ these technologies on the project (which has worked swimmingly so far) there was absolutely no documentation that I could find on the web of the "right way" to do things. So I punted and I got things working so the project could move forward. If I'm designing badly I'd rather know now and fix things than continue on only to run into significant problems later.

                    Thanks again,
                    Mike

                    P.S. Before I submit this reply I'm going to guess at a method of doing things the "right way" and that would be that the JSF project should never interact with EJB entities at all and only with the EJB session beans. That would explain your VO/TO post, however I guess I'm still a bit lost at the concept of the proxy that you refer to. I do use transfer objects to, for example, create an entity from data that a user fills into a form. Thanks again.

                    • 7. Re: How to lazily load collections?
                      Michael Litherland Newbie

                      *sigh* Actually, I'm going to think outloud for a moment. If I have an entity EJB with, say, 20 fields, and I go with the flow and create a transfer object with a matching 20 fields I left with the tedious process of copying 20 fields back and forth every time I want to use the entity. Plus that's a bunch of changes that I have to make should I want to add or remove a field. What a pain! There has to a trick to dealing with these VO/TOs...

                      • 8. Re: How to lazily load collections?
                        Andrew Rubinger Master

                        Hmm...this topic might have left the relevance of the original thread. Give me a shout at the contact info in my profile and we can discuss your concerns more specifically...

                        S,
                        ALR

                        • 9. Re: How to lazily load collections?
                          Wolfgang Knauf Master

                          Hi Michael,

                          here is a sample snippet where accessing the relationship fails with your exception:

                          public MyEntity getEntity(object pk) {
                          MyEntity entity = (MyEntity) entityManager.find(MyEntity.class, pk);
                          //Will NOT load the data !
                          entity.getTdmAlerts();
                          return entity;
                          }


                          I found that just accessing the collection property of the entity does NOT fetch its content.

                          You have to do something which forces the collection to load its items. In the following piece of code I access the collections size:
                          public MyEntity getEntity(object pk) {
                          MyEntity entity = (MyEntity) entityManager.find(MyEntity.class, pk);
                          int iSize = entity.getTdmAlerts().size();
                          return entity;
                          }


                          I suggest to add a parameter "boolean bFetchAlerts" to "getEntity" which controls whether the alerts are loaded or not.

                          I hope this helps

                          Wolfgang