9 Replies Latest reply on Sep 18, 2008 2:59 PM by Guillaume Jeudy

    Merge session-scoped objects in EVERY method call?

    jk;l jkl; Expert

      Is it necessary to do an entityManager.merge(sessionScopedObject) for every method call when entityManager is conversation-scoped, and you want sessionScopedObject and a conversationScopedObject to work with each other?


      I'm finding I have to do


      sessionScopedObject = entityManager.merge(sessionScopedObject);



      before every single method call in order for sessionScopedObject and conversationScopedObject to share the same entityManager.



      @In SessionScopedObject sessionScopedObject;
      @In ConversationScopedObject conversationScopedObject;
      @In EntityManager entityManager;
      



      There must be a simpler way? Please describe it for me if it exists, thanks.

        • 1. Re: Merge session-scoped objects in EVERY method call?
          Tony Herstell Master

          Might be wrong but try using the EXTENDED on your EntityManager. The default is NOT extended.


          Sorry not near my code so might be talking complete foo.


          I must admit I dont recognise this construct at all


          @In SessionScopedObject sessionScopedObject;
          @In ConversationScopedObject conversationScopedObject;
          @In EntityManager entityManager;
          




          This is more familiar to me.




          @PersistenceContext(type=EXTENDED)
          private EntityManager em;



          • 2. Re: Merge session-scoped objects in EVERY method call?
            jk;l jkl; Expert

            If I use @PersistenceContext instead of @In, then I don't get to take advantage of the Seam-managed persistence context that incorporates conversational awareness into the EntityManager.


            Certainly someone must have a way to handle this issue - it's a common enough scenario, where the sessionScopedObject would be the currently logged-in user, and the conversationScopedObject would be one of a number of objects with which that user interacts.

            • 3. Re: Merge session-scoped objects in EVERY method call?
              Guillaume Jeudy Master

              I don't think the behavior you observe is normal. You only need to attach your session scoped object once for the duration of your conversation.


              Of course if your conversation is only a temporary one it will last for 1 request cycle and might appear to you that you need to reattach your session scoped object more than you would like.


              Can you give concise code samples of your usecase?

              • 4. Re: Merge session-scoped objects in EVERY method call?
                jk;l jkl; Expert

                You're right that it's only a temporary conversation. While this application has some long-running conversations, it primarily uses temporary conversations (i.e. requests). I hope this doesn't mean that before every such method call (where session-scoped and conversation-scoped objects interact), which could number hundreds or thousands throughout an application, requires:


                sessionScopedObject = entityManager.merge(sessionScopedObject);



                Maybe my design strategy is just wrong. Let me describe an example of a stateful button, where the value the button has and the action it takes depends on its current state.


                For example, a User (session-scoped) can register to receive notifications from a Notifier (conversation-scoped) - the button displays Receive Notifications and is backed by a receiveNotifications() action on the backing bean. When the User has registered to receive notifications, the button displays Don't Receive Notifications and is backed by cancelNotifications().


                In this case, both receiveNotifications() and cancelNotifications() would require a manual merge at the start of the method! Is there any way to avoid the merge - based on the example, is there another way to design it in terms of what scopes to use or something like that?

                • 5. Re: Merge session-scoped objects in EVERY method call?
                  Guillaume Jeudy Master

                  Can you detail the code sample where User and Notifier entities interact in your usecase? Typically you want to reattach an entity to a persistence context because you potentially want to modify/delete it or trigger relationship lazy loads.

                  • 6. Re: Merge session-scoped objects in EVERY method call?
                    jk;l jkl; Expert

                    The code is below. getNotificationExists() is used to determine whether the Create Notification button, backed by createNotification(), or the Remove Notification button, backed by removeNotification(), is displayed.



                    public boolean getNotificationExists() {
                      user = entityManager.merge(user);
                      for (Notification notification1 : user.getNotifications()) {
                       for (Notification notification2 : notifier.getNotifications()) {
                        if (notification1.equals(notification2)) {
                         return true;
                        }
                       }
                      }
                      return false;
                     }
                     
                     public void removeNotification() {
                      user = entityManager.merge(user);
                      for (Notification notification1 : user.getNotifications()) {
                       for (Notification notification2 : notifier.getNotifications()) {
                        if (notification1.equals(notification2)) {
                         user.getNotifications().remove(notification1);
                         notifier.getNotifications().remove(notification2);
                         entityManager.remove(notification1);
                         return;
                        }
                       }
                      }
                      throw new RuntimeException("Shouldn't have executed");
                     }
                     
                     public void createNotification() {
                      Notification notification = new Notification();
                      notification.setNotifier(notifier);
                      notification.setUser(user);
                      notifier.getNotifications().add(notification);
                      user.getNotifications().add(notification);
                      entityManager.persist(notification);
                     }
                    


                    • 7. Re: Merge session-scoped objects in EVERY method call?
                      Guillaume Jeudy Master

                      the only reason why you need to merge the user is to get the notifications.


                      Have you considered querying with HQL ?


                      entityManager.createQuery("from Notification n where n.user=:user").setParameter("user", user).getResultList();



                      This way you dont need to get notifications by

                      user.getNotifications()

                      and most probably wont need to do merge(user).


                      How is the relationship between user and notification setup? is it bidirectional ?


                      • 8. Re: Merge session-scoped objects in EVERY method call?
                        jk;l jkl; Expert

                        Querying with HQL is a good idea, I'll try that. The relationship between user and notification is pretty flexible - how would it being bidirectional change things here?


                        Do you recommend using HQL for creating and removing the Notification, or simply for checking whether it exists? I don't think you can use insert and delete statements from HQL, so can you suggest a more appropriate way for me to do this than what I'm doing in the example I posted earlier? (I realize the design I have right now is nowhere near ideal - we have flexibility in refactoring the code, so if you can think of a better way I can certainly do it.)


                        Thanks for all your help on this, it's really appreciated.

                        • 9. Re: Merge session-scoped objects in EVERY method call?
                          Guillaume Jeudy Master

                          I'm just saying you need a many-to-one relationship from notification to user if you want to use my HQL query. It looks like you might already have this setup.


                          For persist/remove question, you cannot assume that notifications collection in user will be initialized, for this reason calling remove/add on user.notifications is risky unless you merge the user beforehand.


                          If having an uptodate inmemory user.notifications list is important then I would stick with your current approach albeit with small changes.


                          1. You can setup a persist/delete cascading relationship for user.notifications then you don't need to explicitely persist or remove the notification just adding/removing from the user.notifications collection will cascade the changes at session flush time.


                          Hibernate In Action book suggests that you enforce bidirectional relationship inside a method likewise:


                          class User {
                           ...
                           public void addNotification(Notification notif) {
                               this.notifications.add(notif);
                               notif.setUser(this);
                           }
                          }
                          



                          Just calling

                          user.addNotification(notif);

                          will be sufficient to link both ends of the relationship.