8 Replies Latest reply on Jan 21, 2008 9:37 AM by pmuir

    Problems with list page, persistence, and detached entities

    oberiko

      Hello.

      I seem to be doing something wrong with my Stateful Session Bean in that I can't seem to have it persist properly.

      I have a list page where I want to delete previously entered users. I've put the @DataModel into the page scope, as I want it to be kept available until I leave the page.

      package org.domain.myProject.session;
      
      import static javax.persistence.PersistenceContextType.EXTENDED;
      
      import java.util.List;
      
      import javax.ejb.Remove;
      import javax.ejb.Stateful;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      
      import org.domain.myProject.entity.User;
      import org.domain.myProject.session.local.UserEditLocal;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Destroy;
      import org.jboss.seam.annotations.Factory;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.annotations.datamodel.DataModel;
      import org.jboss.seam.annotations.datamodel.DataModelSelection;
      import org.jboss.seam.faces.FacesMessages;
      import org.jboss.seam.log.Log;
      
      @Stateful
      @Name("userEdit")
      public class UserEditForm implements UserEditLocal {
      
       @Logger
       private Log log;
      
       @In
       FacesMessages facesMessages;
      
       @PersistenceContext(type = EXTENDED)
       private EntityManager em;
      
       @DataModelSelection
       @Out(required=false)
       private User user;
      
       @DataModel(scope=ScopeType.PAGE)
       private List<User> users;
      
       @SuppressWarnings("unchecked")
       @Factory("users")
       public void findUsers(){
       log.info("********************************************* Finding the users");
       if (users == null)
       log.info("users is null");
       else if (users.size() == 0)
       log.info("users has a size of zero");
       else
       log.info("Strange, because users has " +users.size() + " entries");
       users = em.createQuery("from User u").getResultList();
       }
      
       public void remove(){
       log.info("Deleting user " +user.getName());
       em.remove(user);
       users.remove(user);
       }
      
       public void update() {
       //TODO write the update method
       }
      
       @Destroy @Remove
       public void destroy() {}
      }
      


      The following is my console log:
      19:48:04,917 INFO [STDOUT] Hibernate:
       insert
       into
       User
       (id, name)
       values
       (null, ?)
      19:48:04,917 INFO [STDOUT] Hibernate:
       call identity()
      19:48:04,917 INFO [SaveUserAction] John was saved.
      19:48:08,401 INFO [UserEditForm] ********************************************* Finding the users
      19:48:08,401 INFO [UserEditForm] users is null
      19:48:08,417 INFO [STDOUT] Hibernate:
       select
       user0_.id as id0_,
       user0_.name as name0_
       from
       User user0_
      19:48:11,323 INFO [UserEditForm] ********************************************* Finding the users
      19:48:11,323 INFO [UserEditForm] users is null
      19:48:11,323 INFO [STDOUT] Hibernate:
       select
       user0_.id as id0_,
       user0_.name as name0_
       from
       User user0_
      19:48:11,323 INFO [UserEditForm] Deleting user Vader
      19:48:11,339 INFO [STDOUT] Hibernate:
       delete
       from
       User
       where
       id=?
      19:48:26,495 INFO [UserEditForm] ********************************************* Finding the users
      19:48:26,495 INFO [UserEditForm] users is null
      19:48:26,495 INFO [STDOUT] Hibernate:
       select
       user0_.id as id0_,
       user0_.name as name0_
       from
       User user0_
      19:48:26,495 INFO [UserEditForm] Deleting user Skywalker
      19:48:26,511 INFO [STDOUT] Hibernate:
       delete
       from
       User
       where
       id=?
      


      And this is the page I'm calling it from:
      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:s="http://jboss.com/products/seam/taglib"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:h="http://java.sun.com/jsf/html">
      
      <f:view>
       <h:messages />
       <h:form>
       <h:dataTable value="#{users}" var="user">
       <h:column>
       <f:facet name="header">
       <h:outputText value="Id" />
       </f:facet>
       <h:outputText value="#{user.id}" />
       </h:column>
       <h:column>
       <f:facet name="header">
       <h:outputText value="Name" />
       </f:facet>
       <h:outputText value="#{user.name}" />
       </h:column>
       <h:column>
       <f:facet name="header">
       <h:outputText value="Actions" />
       </f:facet>
       <s:button action="#{userEdit.remove()}" value="S. Delete"/>
       <h:commandButton action="#{userEdit.remove()}" value="A. Delete" />
       </h:column>
       </h:dataTable>
       </h:form>
       <s:button view="/user.xhtml" value="Return"/>
      </f:view>
      
      </html>
      
      


      I think I understand that the s:button, since it's not passing the form back, is basically treating everything as a new page, which is why it's not being persisted (that's what I used in the above logs). When I try the <h:commandButton> though, I get the following:

      Exception during request processing:
      Caused by javax.servlet.ServletException with message: "#{userEdit.remove()}: javax.ejb.EJBTransactionRolledbackException: Removing a detached instance org.domain.myProject.entity.User#7"


      Is there a way, barring putting the page in the session scope, to keep the entities from being detached?

        • 1. Re: Problems with list page, persistence, and detached entit
          nickarls

          Tried using long-running conversations? Otherwise, you could just merge() the entity before deleting.

          • 2. Re: Problems with list page, persistence, and detached entit
            oberiko

            If I put the SFSB into a long running conversation, then what would be the useage of the PAGE scope?

            • 3. Re: Problems with list page, persistence, and detached entit
              oberiko

              Here's how I want it to work:


              * Save users to a database on the entry page
              * On navigation to the list page, pull users from the database into a list in the scope and display them
              * Do not call the database to update the list since it's in the scope
              * When leaving the page, remove all traces from the scope


              Now, I got that working with the following:

              package org.domain.myProject.session;
              
              import static javax.persistence.PersistenceContextType.EXTENDED;
              
              import java.util.List;
              
              import javax.ejb.Remove;
              import javax.ejb.Stateful;
              import javax.persistence.EntityManager;
              import javax.persistence.PersistenceContext;
              
              import org.domain.myProject.entity.User;
              import org.domain.myProject.session.local.UserEditLocal;
              import org.jboss.seam.ScopeType;
              import org.jboss.seam.annotations.Destroy;
              import org.jboss.seam.annotations.Factory;
              import org.jboss.seam.annotations.In;
              import org.jboss.seam.annotations.Logger;
              import org.jboss.seam.annotations.Name;
              import org.jboss.seam.annotations.Out;
              import org.jboss.seam.annotations.Scope;
              import org.jboss.seam.annotations.datamodel.DataModel;
              import org.jboss.seam.annotations.datamodel.DataModelSelection;
              import org.jboss.seam.contexts.Contexts;
              import org.jboss.seam.faces.FacesMessages;
              import org.jboss.seam.log.Log;
              
              @Stateful
              @Name("userEdit")
              @Scope(ScopeType.SESSION)
              public class UserEditForm implements UserEditLocal {
              
               @Logger
               private Log log;
              
               @In
               FacesMessages facesMessages;
              
               @PersistenceContext(type = EXTENDED)
               private EntityManager em;
              
               @DataModelSelection
               @Out(required=false)
               private User user;
              
               @DataModel(scope=ScopeType.PAGE)
               private List<User> users;
              
               @SuppressWarnings("unchecked")
               @Factory("users")
               public void findUsers(){
               log.info("********************************************* Finding the users");
               if (users == null)
               log.info("users is null");
               else if (users.size() == 0)
               log.info("users has a size of zero");
               else
               log.info("Strange, because users has " +users.size() + " entries");
              
               users = em.createQuery("from User u").getResultList();
              
               }
              
               public void remove(){
               log.info("Deleting " +user.getName());
               em.remove(user);
               users.remove(user);
               }
              
               public void clear(){
               log.info("Clearing the userEdit bean from the session");
               Contexts.getSessionContext().remove("userEdit");
               }
              
               public void update() {
               //TODO write the update method
               }
              
               @Destroy @Remove
               public void destroy() {}
              }
              


              package org.domain.myProject.session;
              
              import javax.ejb.Stateless;
              import javax.persistence.EntityManager;
              import javax.persistence.PersistenceContext;
              
              import org.domain.myProject.entity.User;
              import org.domain.myProject.session.local.SaveUserLocal;
              import org.jboss.seam.annotations.In;
              import org.jboss.seam.annotations.Logger;
              import org.jboss.seam.annotations.Name;
              import org.jboss.seam.annotations.Out;
              import org.jboss.seam.contexts.Context;
              import org.jboss.seam.contexts.Contexts;
              
              import org.jboss.seam.faces.FacesMessages;
              import org.jboss.seam.log.Log;
              
              @Stateless
              @Name("saveUser")
              public class SaveUserAction implements SaveUserLocal {
              
               @Logger
               private Log log;
              
               @In
               FacesMessages facesMessages;
              
               @PersistenceContext
               private EntityManager em;
              
               /* We have an '@Out' on this to push back user (after any modifications we make
               * in our bean here) onto the page. Since we're nullifying user in our save method,
               * we need to state that it's not required to have any value.
               */
               @In @Out(required=false)
               private User user;
              
               public void saveUser() {
               //Simple persisting with the EntityManager
               em.persist(user);
              
               //Note that the id of our user was generated and populated.
               facesMessages.add("User was saved with an id of "+ user.getId());
              
               log.info(user.getName() +" was saved.");
              
               //We're nullifying user so that the page is blank after saving.
               user = null;
              
               //All items in the session context, only here for testing the list page
               for (String s:Contexts.getSessionContext().getNames()){
               if (s.startsWith("org.jboss") || s.startsWith("javax") || s.startsWith("org.ajax4jsf"))
               continue;
               log.info(s + " is in the session");
               }
               }
              }
              
              


              And my .xhtml files
              <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
              <html xmlns="http://www.w3.org/1999/xhtml"
               xmlns:s="http://jboss.com/products/seam/taglib"
               xmlns:f="http://java.sun.com/jsf/core"
               xmlns:h="http://java.sun.com/jsf/html">
              
              <f:view>
               <h:form>
               <s:validateAll>
               Name:
               <h:inputText value="#{user.name}" required="true"
               requiredMessage="You need to enter a name"/>
               <br />
               <h:commandButton value="Save" action="#{saveUser.saveUser()}" />
               <s:button value ="List" view="/listUsers.xhtml"/>
               </s:validateAll>
               </h:form>
              <h:messages />
              </f:view>
              
              </html>
              


              <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
               "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
              <html xmlns="http://www.w3.org/1999/xhtml"
               xmlns:s="http://jboss.com/products/seam/taglib"
               xmlns:f="http://java.sun.com/jsf/core"
               xmlns:h="http://java.sun.com/jsf/html">
              
              <f:view>
               <h:messages />
               <h:form>
               <h:dataTable value="#{users}" var="user">
               <h:column>
               <f:facet name="header">
               <h:outputText value="Id" />
               </f:facet>
               <h:outputText value="#{user.id}" />
               </h:column>
               <h:column>
               <f:facet name="header">
               <h:outputText value="Name" />
               </f:facet>
               <h:outputText value="#{user.name}" />
               </h:column>
               <h:column>
               <f:facet name="header">
               <h:outputText value="Actions" />
               </f:facet>
               <s:button action="#{userEdit.remove()}" value="S. Delete"/>
               <h:commandButton action="#{userEdit.remove()}" value="H. Delete" />
               </h:column>
               </h:dataTable>
               </h:form>
               <s:button action="#{userEdit.clear()}" view="/user.xhtml" value="S. Return"/>
              </f:view>
              
              </html>
              


              And here's my log (trimmed for brevity)
              11:41:01,347 INFO [SaveUserAction] Luke was saved.
              11:41:06,809 INFO [SaveUserAction] Skywalker was saved.
              11:41:11,567 INFO [SaveUserAction] Darth was saved.
              11:41:15,417 INFO [SaveUserAction] Vader was saved.
              11:41:18,813 INFO [SaveUserAction] Yoda was saved.
              11:41:25,605 INFO [SaveUserAction] I forgot about Yoda! was saved.
              11:41:40,770 INFO [UserEditForm] ********************************************* Finding the users
              11:41:40,770 INFO [UserEditForm] users is null
              11:41:45,043 INFO [UserEditForm] Deleting I forgot about Yoda!
              11:41:48,548 INFO [UserEditForm] Deleting Darth
              11:41:54,762 INFO [UserEditForm] Clearing the userEdit bean from the session
              11:42:04,684 INFO [SaveUserAction] Solo was saved.
              11:45:11,096 INFO [UserEditForm] ********************************************* Finding the users
              11:45:11,096 INFO [UserEditForm] users is null
              11:45:18,702 INFO [UserEditForm] Deleting Vader
              


              Now, with that working (though not as cleanly as I'd like), I started trying to figure out what I could ditch to make it simpler.

              I started by getting rid of the page scope on my DataModel and adding a check to see the users being removed from the session:

              ...
              // @DataModel(scope=ScopeType.PAGE)
               @DataModel
               private List<User> users;
              ...
              public void clear(){
               log.info("Clearing the userEdit bean from the session");
               Contexts.getSessionContext().remove("userEdit");
              
               if (Contexts.getSessionContext().isSet("users")){
               log.info("Clearing the users from the session");
               Contexts.getSessionContext().remove("users");
              
               if (Contexts.getSessionContext().isSet("users"))
               log.info("You're still here?");
               }
              ...
              


              But for some reason, now the "users" keeps getting plugged back into the session, even though I removed (and verified removing) it.

              11:51:23,638 INFO [SaveUserAction] Jean Luc was saved.
              11:51:27,457 INFO [SaveUserAction] Picard was saved.
              11:51:30,696 INFO [SaveUserAction] Data was saved.
              11:51:34,155 INFO [SaveUserAction] Worf was saved.
              11:51:37,943 INFO [UserEditForm] ********************************************* Finding the users
              11:51:37,943 INFO [UserEditForm] users is null
              11:51:51,058 INFO [UserEditForm] Deleting Picard
              11:52:00,119 INFO [UserEditForm] Deleting Data
              11:52:04,908 INFO [UserEditForm] Clearing the userEdit bean from the session
              11:52:04,908 INFO [UserEditForm] Clearing the users from the session
              11:52:28,634 INFO [SaveUserAction] Locutus was saved.
              11:52:28,634 INFO [SaveUserAction] users is in the session
              11:52:56,758 INFO [SaveUserAction] Q was saved.
              11:52:56,758 INFO [SaveUserAction] users is in the session
              


              So it looks like I need the page scope.

              So, is my top way, of putting the whole bean in the session-scope and the data model in the page-scope the cleanest way of doing this?

              I'm trying to avoid the session-scope as much as possible since, coming from a Struts background, I know the hassle (in both maintenaince and leaks) it can cause, especially when several other people start working on something with you.

              Would it be of any use to start looking at creating a Seam Managed Persistence Context? I'm using EAR deployment (so I can use EJB's) and I know that I can't use the default of:
              @In private EntityManager entityManager

              Due to http://jira.jboss.org/jira/browse/JBIDE-1409?decorator=printable


              Also, why does "users" keep popping back into the session when not assigned to the page-scope, even after removing it?

              I know this is all quite the mouthful, but anyone have any suggestions?

              • 4. Re: Problems with list page, persistence, and detached entit
                oberiko

                It seems to work just as well by using the conversation scope, with less code and, of course, a smaller footprint.

                package org.domain.myProject.session;
                
                import static javax.persistence.PersistenceContextType.EXTENDED;
                
                import java.util.List;
                
                import javax.ejb.Remove;
                import javax.ejb.Stateful;
                import javax.persistence.EntityManager;
                import javax.persistence.PersistenceContext;
                
                import org.domain.myProject.entity.User;
                import org.domain.myProject.session.local.EditUserLocal;
                import org.jboss.seam.ScopeType;
                import org.jboss.seam.annotations.Begin;
                import org.jboss.seam.annotations.Destroy;
                import org.jboss.seam.annotations.End;
                import org.jboss.seam.annotations.Factory;
                import org.jboss.seam.annotations.In;
                import org.jboss.seam.annotations.Logger;
                import org.jboss.seam.annotations.Name;
                import org.jboss.seam.annotations.Out;
                import org.jboss.seam.annotations.Scope;
                import org.jboss.seam.annotations.datamodel.DataModel;
                import org.jboss.seam.annotations.datamodel.DataModelSelection;
                import org.jboss.seam.faces.FacesMessages;
                import org.jboss.seam.log.Log;
                
                @Stateful
                @Name("editUser")
                @Scope(ScopeType.CONVERSATION)
                public class EditUserForm implements EditUserLocal {
                
                 @Logger
                 private Log log;
                
                 @In
                 FacesMessages facesMessages;
                
                 @PersistenceContext(type = EXTENDED)
                 private EntityManager em;
                
                 @DataModelSelection
                 @Out(required=false)
                 private User user;
                
                 @DataModel
                 private List<User> users;
                
                 @SuppressWarnings("unchecked")
                 @Factory("users")
                 @Begin
                 public void findUsers(){
                 log.info("********************************************* Finding the users");
                 if (users == null)
                 log.info("users is null");
                 else if (users.size() == 0)
                 log.info("users has a size of zero");
                 else
                 log.info("Strange, because users has " +users.size() + " entries");
                
                 users = em.createQuery("from User u").getResultList();
                 }
                
                 public void remove(){
                 log.info("Deleting " +user.getName());
                 em.remove(user);
                 users.remove(user);
                 }
                
                 @End
                 public void clear(){
                 //We're clearing user since it is being outjected and would otherwise populate the entry page.
                 user = null;
                 }
                
                 public void update() {
                 log.info("Updating " + user.getId());
                 em.persist(user);
                 }
                
                 @Destroy @Remove
                 public void destroy() {}
                }
                


                Using it like this though, doesn't require the ScopeType.PAGE at all, which was what I originally wanted.

                Is this kind of situation just not applicable to using the PAGE scope? If not, when would we use it?

                • 5. Re: Problems with list page, persistence, and detached entit
                  pmuir

                  Phew, long post.

                  To keep entites from being detached use an SMPC with a long running conversation.

                  • 6. Re: Problems with list page, persistence, and detached entit
                    bludginozzie

                    I find that DataModelSelection stuff really flakey. You could just use tradional JEE development methods for this. This code should work in any view.

                    Eg: In your view;

                    <s:button action="#{userEdit.remove(user.id)}" value="S. Delete"/>


                    Then in your action;
                    public void remove(Long id){
                     User removeUser = (User) em.find(User.class, id);
                     if (removeUser == null) {
                     // TODO: Already Deleted!! Someone beat you to it
                     } else {
                     log.info("Deleting " +removeUser.getName());
                     em.remove(removeUser);
                     users.remove(removeUser );
                     }
                    }
                    


                    Forgive any typos since I didn't test this code of course.

                    Good Luck!


                    • 7. Re: Problems with list page, persistence, and detached entit
                      oberiko

                      Thanks.

                      Quick question Pete, where can I find how to do a SMPC when deploying in an ear? Is there a workaround to the below JIRA issue?

                      http://jira.jboss.org/jira/browse/JBIDE-1409?decorator=printable

                      • 8. Re: Problems with list page, persistence, and detached entit
                        pmuir

                        Thats just a validation error with JBoss Tools right? So ignore the warning.