13 Replies Latest reply on Aug 6, 2009 1:32 AM by asookazian

    Beans that don't share the same entity manager in same conversation

    dhinojosa

      I have a long term conversation set up..
      I have two beans:


      @Name("officeServiceBean")
      @Scope(ScopeType.CONVERSATION)
      public class OfficeServiceBean {
          private EntityManager entityManager;
          private FindUserByFullNameService findUserByFullNameService;
          private String managerName;
      
          @In
          public void setEntityManager(EntityManager entityManager) {
              this.entityManager = entityManager;
          }
      
          @In(value = "findUserByFullNameServiceBean", create = true)
          public void setFindUserByFullNameService(FindUserByFullNameService findUserByFullNameService) {
              this.findUserByFullNameService = findUserByFullNameService;
          }
      
          ...
      



      @Name("findUserByFullNameServiceBean")
      @Scope(ScopeType.CONVERSATION)
      public class FindUserByFullNameServiceBean implements FindUserByFullNameService {
          private EntityManager entityManager;
      
          /**
           * Sets the EntityManager
           *
           * @param entityManager the entityManager
           */
          @In
          public void setEntityManager(EntityManager entityManager) {
              this.entityManager = entityManager;
          }
      
          /**
           * {@inheritDoc}
           */
          public User findUserByFullName(String fullName) throws NoResultException {
              Query query = entityManager.createQuery("SELECT u from User u where UPPER(firstName)" +
                      " || ' ' || UPPER(lastName) = :fullName");
              query.setParameter("fullName", fullName.toUpperCase());
              return (User) query.getSingleResult();
          }
      }
      



      Question is: why don't both of these beans share the same entity manager since they are both within the same conversation?
      Currently the setFindUserByFullNameService sets fine but uses a different entityManager. Please note that the FindUserByFullNameServiceBean is created at injection (create = true).  Currently this is causing a javax.persistence.PersistenceException: org.hibernate.PersistentObjectException

        • 1. Re: Beans that don't share the same entity manager in same conversation
          dhinojosa

          So it seems that is true, when are injecting into a conversation bean using create = true that bean being injected will not use the same entitymanager.


          • 2. Re: Beans that don't share the same entity manager in same conversation
            asookazian

            You're using setter injection in this case.  According the to Dependency Injection in EJB 3 refcard:



            Although setter injection might seem like a
            little more work, it provides a couple of distinct
            advantages. First, it is easier to unit-test by
            invoking the public setter method from a testing
            framework like JUnit. Second, it is easier to put
            initialization code in the setter if you need it.

            So I'm assuming you're using setter injection for one or both of those reasons (most likely the former).


            Anyways, that's an interesting find that I haven't seen discussed elsewhere.


            Where/how does the LRC begin?


            And are you referring to a different EntityManager interface instance or the PersistenceContext that it manages are different?


            A SMPC spans a LRC so any conversation-scoped components involved in that LRC should share the same SMPC as long as it's the same EntityManager tied to the same persistence unit, which should be the case in your example code...


            Do you get different behavior if you use property/attribute injection rather than setter injection in both components?

            • 3. Re: Beans that don't share the same entity manager in same conversation
              joblini

              Hello Daniel,


              How have you determined that a different entity manager is being used?


              Regards,
              Ingo

              • 4. Re: Beans that don't share the same entity manager in same conversation
                dhinojosa

                Because I had to use merge()! ;)

                • 5. Re: Beans that don't share the same entity manager in same conversation
                  dhinojosa

                  OK, so maybe not


                  I have an Office object called office and a User object called manager.  Office has a @ManyToOne relationship to User through it's manager member.  I am using a SMPC and before the flush that throws the Detached Entity Exception, both objects are in a managed state and I verified it with:


                  System.out.println("contains manager?" + entityManager.contains(manager));
                  System.out.println("contains office?" + entityManager.contains(office));
                  



                  So when I do a flush() that is when the Exception is thrown.  Now I am leaning towards an issue with @ManyToOne for some reason I think that Hibernate detaches the User in that relationship somehow during the flush.


                  • 6. Re: Beans that don't share the same entity manager in same conversation
                    joblini

                    Have you actually compared the object identifiers in debug to be certain that you are dealing with two instances of entity manager?

                    • 7. Re: Beans that don't share the same entity manager in same conversation
                      joblini

                      Are you sure that you are setting both sides of the relationship before flushing?

                      • 8. Re: Beans that don't share the same entity manager in same conversation
                        dhinojosa

                        It's a unidirectional relationship.

                        • 9. Re: Beans that don't share the same entity manager in same conversation
                          dhinojosa

                          No, but I don't believe I am using the same entitymanager anymore. Since I verified that both my beans were attached before sending it to a flush.

                          • 10. Re: Beans that don't share the same entity manager in same conversation
                            dhinojosa

                            Woops!  They are using DIFFERENT entity managers!!! WTF?


                            Ok. So lets say I will update an office, and I have this in my pages.xml


                            <page view-id="/management/office/update.xhtml" login-required="true">
                                    <restrict>#{s:hasRole('Management')}</restrict>
                                    <begin-conversation join="true"/>
                                    <param name="id" value="#{loadOfficeFactory.id}"
                                           required="true" converterId="javax.faces.Long"/>
                                    <navigation from-action="#{officeServiceBean.update}">
                                        <rule if-outcome="success">
                                            <end-conversation/>
                                            <redirect view-id="/management/office/view.xhtml"/>
                                        </rule>
                                    </navigation>
                            </page>
                            


                            Here is the loadOfficeFactory that is referenced in the param above.


                            @Name("loadOfficeFactory")
                            @Scope(ScopeType.CONVERSATION)
                            public class LoadOfficeFactory implements OfficeFactory{
                                private EntityManager entityManager;
                                private Long id;
                            
                                @In
                                public void setEntityManager(EntityManager entityManager) {
                                    this.entityManager = entityManager;
                                }
                            
                                public void setId(Long id) {
                                    this.id = id;
                                }
                            
                                public Long getId() {
                                    return id;
                                }
                            
                                /**
                                 * Instantiates an Office object
                                 *
                                 * @return an office object
                                 */
                                @Factory("office")
                                public Office createOffice() {
                                    System.out.println("loading office with entityManager:" + entityManager);
                                    return entityManager.find(Office.class, id);
                                }
                            }
                            



                            Now...in my update.xhtml page I have the following


                              <h:inputText size="30" label="#{messages.manager}" id="manager"
                                             value="#{officeServiceBean.managerName}"
                                             required="true"/>
                                <rich:suggestionbox height="200" width="200"
                                                    suggestionAction="#{userAutoComplete.autocomplete}" var="user"
                                                    for="manager" fetchValue="#{user.firstName} #{user.lastName}" id="user_suggestion"
                                                    nothingLabel="No users found">
                                    <h:column>
                                        <h:outputText value="#{user.firstName}"/>
                                    </h:column>
                                    <h:column>
                                        <h:outputText value="#{user.lastName}"/>
                                    </h:column>
                                </rich:suggestionbox>
                            



                            I use that inputText field and map it to String property in my officeServiceBean.  I use that property to do a lookup in the entity manager to return the user with that first and last name (which is done by another service called findUserByFullNameService. See code below:


                            @Transactional
                                public String update(Office office) throws SystemException {
                                    User manager = null;
                                    try {
                                        manager = findUserByFullNameService.findUserByFullName(managerName);   //find the manager object, I also print out the entity manager in this service
                            
                                    } catch (NoResultException nre) {
                                        statusMessages.addFromResourceBundleOrDefault(
                                                StatusMessage.Severity.ERROR,
                                                "managerNotFound",
                                                "The manager \'{0}\' has already been entered",
                                                managerName);
                                        return "failure";
                                    }
                                    System.out.println("flushing office with entityManager:" + entityManager);
                            
                                    System.out.println("contains manager?" + entityManager.contains(manager));
                                    System.out.println("contains office?" + entityManager.contains(office));
                            
                                    office.setManager(manager);                    //apply the manager to the office.                
                                    office.setCreatedDate(calendar);
                                    office.setUpdatedDate(calendar);
                                    auditItem.setCreatedDate(calendar);
                                    auditItem.setUpdatedDate(calendar);
                                    auditItem.setDate(calendar);
                                    auditItem.setVerb("Created");
                                    auditItem.setUser(loggedInUser);
                                    office.addAuditItem(auditItem);
                                    entityManager.flush();                         //KABLOOEY!!!!!
                                    statusMessages.addFromResourceBundleOrDefault(
                                            StatusMessage.Severity.INFO,
                                            "officeUpdated",
                                            "The office \'{0}\' has been updated",
                                            office.getName());
                                    return "success";
                                }
                            



                            I have verified that when I run this thing that at flush, office initially for the page  was loaded by one entityManager, another office loaded, user loaded, and flush done by yet another entityManager.  But Seam is supposed guarantee(?) that within a Long-term conversation that everything is loaded from the same entityManager including the pages but it isn't.


                            The results?


                            loading office with entityManager:org.jboss.seam.persistence.EntityManagerProxy@f79c23  //I think this is the one on the page
                            
                            loading office with entityManager:org.jboss.seam.persistence.EntityManagerProxy@187748f
                            
                            finding user with entityManager:org.jboss.seam.persistence.EntityManagerProxy@187748f
                            
                            flushing office with entityManager:org.jboss.seam.persistence.EntityManagerProxy@187748f
                            



                            Now here is what I am doing differently and what may be causing this.  I am using the office from the page to do the updating


                             <h:form id="office_update_form">
                                        <ui:include src="edit.xhtml"/>
                                        <h:commandButton id="update_office" value="#{messages.update}"
                                                         action="#{officeServiceBean.update(office)}"/>    //I am calling update through a method on the xhtml page!!!!!!
                                    </h:form>
                            
                            



                            Therefore the page has items loaded using a different entityManager than the one used in the backend and voila! problem found.

                            • 11. Re: Beans that don't share the same entity manager in same conversation
                              dhinojosa

                              Solved!  Thanks Arbi & Ingo


                              @Name("loadOfficeFactory")
                              @Scope(ScopeType.CONVERSATION)
                              public class LoadOfficeFactory implements OfficeFactory{
                                  private EntityManager entityManager;
                                  private Long id;
                              
                                  @In
                                  public void setEntityManager(EntityManager entityManager) {
                                      this.entityManager = entityManager;
                                  }
                              
                                  public void setId(Long id) {
                                      this.id = id;
                                  }
                              
                                  public Long getId() {
                                      return id;
                                  }
                              
                                  /**
                                   * Instantiates an Office object
                                   *
                                   * @return an office object
                                   */
                                  @Begin                                                                            //Programmic Conversation Start HERE!  Rock and Roll!  removed from pages.xml
                                  @Factory("office")
                                  public Office createOffice() {
                                      System.out.println("loading office with entityManager:" + entityManager);
                                      return entityManager.find(Office.class, id);
                                  }
                              }
                              



                              • 12. Re: Beans that don't share the same entity manager in same conversation
                                asookazian

                                That's a declarative promotion to LRC (when the method successfully completes IIRC).  Programmatic promotion to LRC is via the Conversation API.


                                And why did this solve your problem?  Is there only one EntityManager instance involved in your code/use-case now?

                                • 13. Re: Beans that don't share the same entity manager in same conversation
                                  asookazian

                                  I still do not understand this thread/problem fully.  Why were there more than one instance of EntityManager interface and does this happen to anybody else?


                                  When you called entityManager.flush(), were all involved entities in the transaction currently managed in the PC at that point in time?  If not, did you try to merge() them into the PC so that they are managed?