10 Replies Latest reply on Mar 21, 2006 12:53 PM by binario

    Lazy loading design - surely I can't be right?

    binario

      Hi all,

      Here's my architecture (standard enough)

      layer 1: jsps -->
      layer 2: struts actions -->
      layer 3: ejb3 stateless session bean managers -->
      layer 4: ejb3 domain objects

      I have a one-to-many relationship between User and Order, ie a User has many Orders.

      Now, I want to load the orders lazily. No problem, I specify the fetch type to be lazy on the relationship.

      So, when i get back a user, I don't get his orders.

      In order to get all his orders, I code a second method in one of my stateless session beans to specifically get all the orders for a given user

      eg,


      public List getOrdersForUser(String userName){
      //first get the user for the username
      User u = entityManager.find(User.class,userName);
      //then call
      return user.getOrders();
      }


      However, when I call this method I am getting the following exception


      failed to lazily initialize a collection of role: model.User.orders, no session or session was closed" when accessing the data of a "one-to-many" relation.


      not good.

      Now I have read the following post:

      http://www.jboss.com/index.html?module=bb&op=viewtopic&t=73005

      And in here it discusses the issue of transactions as the possible cause of the problem.

      I was under the impression that as soon as I entered a method in a stateless session bean, under the hood the hibernate session is re-opened and therefore I can access any level in the graph that I want. This is what I would expect with spring/hibernate etc.

      Is this not the case with ejb3 ???

      Do I have to explicitly set the transaction attribute on all methods to get this working??

      This is really a case of me not understanding the design as I thought I did. I understand the List will be detatched from the session when I return it back to the web tier (which is where the exception is being thrown), but the information is retrieved from the database in the stateless session bean, so there should be no problem??

      please help!

      thanks ,

      binario

        • 1. Re: Lazy loading design - surely I can't be right?
          binario

          anyone got any ideas?

          • 2. Re: Lazy loading design - surely I can't be right?
            nickthegreat

             


            was under the impression that as soon as I entered a method in a stateless session bean, under the hood the hibernate session is re-opened and therefore I can access any level in the graph that I want.


            you are under the right impression.

            in the stateless/stateful session bean you CAN access any level in the object graph ... of course you cant in any "higher" level

            for me this always worked ok ... so it must be something stupid ...

            plz try this:

            public List getOrdersForUser(String userName)
            {
            User u = entityManager.find(User.class,userName);
            // a check if a user with this name is actualy found wouldn't hurt
            List orders = u.getOrders();
            u.size(); // dummy operaton to initialize (fill) lazy collection
            return L;
            }

            or a completely different query:
            (not sure if really correct, but something like that)
            ...
            em.createQuery("select o from orders o where o.user.name=:name");
            em.setParamter("name", userName);
            return em.getResultList();

            hth, ntg


            • 3. Re: Lazy loading design - surely I can't be right?
              nickthegreat

              correction:
              u.size(); // dummy operaton to initialize (fill) lazy collection
              replace with ->
              orders.size();

              • 4. Re: Lazy loading design - surely I can't be right?
                binario

                Hi NickTheGreat,

                First off, you are great to reply. Thanks so much. I was loosing faith :-).

                Anyway, yes, I too have seen that calling the dummy method list.size() to initialize the collection works, however, my question is - why is this needed at all????

                Anyway, if that is the fix everyone is using fair enough, but I have to say I think it's pretty awful. Surely when you do a call to --> u.getOrders(), eg:

                {code}
                public List getOrdersForUser(String userName)
                {
                User u = entityManager.find(User.class,userName);
                // a check if a user with this name is actualy found wouldn't hurt
                return = u.getOrders();
                }
                {code}

                you would expect the collection to be accessed.

                Anyway, I think this will lead to loads of problems, but thanks very much indeed for your help.

                cheers,
                Binario

                • 5. Re: Lazy loading design - surely I can't be right?
                  nickthegreat


                  yes, I also find this rather unintuitive.
                  a lazy collection "per se" is a just an "empty" proxy object, that's only "filled with live" when actually being accessed.
                  returning a "pointer" of the proxy doesnt count as being accessed.

                  why it is designed that way -> you have to ask the hibernate creators :)
                  probably performance ...

                  • 6. Re: Lazy loading design - surely I can't be right?
                    nickthegreat

                    Addition:

                    Yes, there is a "cleaner" workaround than e.g .size() on lazy collections before returning them if I remeber correctly.
                    But it's Hibernate specific ... something like Hibernate.initialize(lazyset) (too lazy to look up in hibernate reference, but it should be similar)

                    If you are using EJB3 because it's a standard rather than a particular implementation, Hibernate specific code aint good I guess.

                    Dont know if there's a real "clean" plain EJB3 solution, both avoiding dummy access to lazy collection before returing it or using hibernate specific code.

                    Personaly I prefer the .size() one, just get sure that it's good documented.

                    • 7. Re: Lazy loading design - surely I can't be right?
                      binario

                      Of all the options, I agree with you, the .size() is definately the best - but I will restate my opinion in the hope that someone on the hibernate/ejb3 team reads it. I think this is a TERRIBLE design flaw.

                      Also, if the jboss ejb3 release goes like this then surely it should be documented CLEARLY. Please can this be an example on the docs.jboss.com/ejb3 tutorial pages.

                      • 8. Re: Lazy loading design - surely I can't be right?
                        elkner

                         

                        "binario" wrote:
                        I think this is a TERRIBLE design flaw.


                        Hmm, IMHO this is better, than blowing up the required code and introduce a more or less big performance problem and other trouble.

                        it should be documented CLEARLY.


                        Yepp. +100

                        • 9. Re: Lazy loading design - surely I can't be right?
                          mazz

                          I'm not sure its a "terrible" design flaw. I think your wording might be overstating the issue. Is it unintuitive? Probably.

                          A previous post said it right - just getting the reference does not imply you are reading/accessing the data. It's conceivable you are just going to pass that refererence around and not yet access any of the data in the collection.

                          If you want to force the collection to be loaded, use EAGER fetching or don't use the find method; instead, perform a query that loads the collection with an inner join.

                          • 10. Re: Lazy loading design - surely I can't be right?
                            binario

                            Hi guys,

                            thanks for the responses. I've looked some more at this and I see that this is what hibernate does under the hood by using, as Nick said, the "Hibernate.initialize(Object proxy) method.

                            I agree that "terrible" is probably overstated :-), but I definately think it is unintuitive and probably at least ... poor. For example, why would you ever want to call

                            Collection orders = user.getOrders
                            


                            and not use orders for something.


                            It's conceivable you are just going to pass that refererence around and not yet access any of the data in the collection.


                            That's where I think I disagree with you. I can't think of any situation where this might be the case. Surely, if you call get on something, you expect to get it back initialized if we're in the session.

                            At any rate, I don't mind at all, and I very much appreciate the help and I would just reiterate, clear documentation will help others avoid this non-obvious problem.

                            thanks again,
                            binario