3 Replies Latest reply on Sep 14, 2006 8:00 PM by javidjamae

    EntityManager merge and the seam-registration example.

    hchafi

      I have a question about merge and persist. I understand the basic difference between the two, but I am trying to undertand a bit more of the behavior of merge.

      I modified the User.java class in the seam-registration example and added an Id field.

       @Id @GeneratedValue(strategy=GenerationType.AUTO)
       public Long getId() {
       return id;
       }
      


      The problem I ran into is the "detached entity passed to persist" as I was trying to register multiple users. I guess persist expects the id field not to be set. At least that is what I gathered from other posts.

      I switched to merge, and it works. I don't understand why it should work though. Since User is a session scoped entity bean, doesn't it mean it should remain alive during the whole session. in which case shouldn't the merge just update the same user instead of creating a new one. I inspected the database, and it does indeed create multiple users.

      Can someone point to the hole in my understanding.

      -hc

        • 1. Re: EntityManager merge and the seam-registration example.

          Persist is used to insert a new instance of an object to the database.
          The underlying persistence engine (most probably hibernate) needs to know if your object is is a new object, or if is a detached instance (an instance already present in the database but not associated with the session). The usual mechanism for doing this is by inspecting the @Id field for a null/non-null value. (There are other mechanisms which you can employ using hibernate - but I'm not 100% sure if these are std J2EE 5 ways)

          [For more information on this check out the hibernate forums/docs http://forum.hibernate.com]

          So - if you try and persist (insert) an object that already has an identifer - you are going to get the exception you mentioned.

          Entity beans by default are bound to the conversational context. Have you explicitly bound the user to the session? Show us the code that is creating the user and the user object.

          • 2. Re: EntityManager merge and the seam-registration example.
            hchafi

             

            
            import static org.jboss.seam.ScopeType.SESSION;
            
            import java.io.Serializable;
            ...
            import org.jboss.seam.annotations.Scope;
            
            @Entity
            @Name("user")
            @Scope(SESSION)
            @Table(name="users",
             uniqueConstraints={@UniqueConstraint(columnNames={"username"})})
            public class User implements Serializable {
            
            
             private static final long serialVersionUID = 9093363133205674398L;
            
             private Long id;
             private String username = null;
             private String password = null;
             private Boolean seller = null;
             private SellerInfo sellerInfo = null;
             private ContactInfo contactInfo = null;
             private String firstName;
             private String lastName;
            
             public String getFirstName() {
             return firstName;
             }
             public void setFirstName(String firstName) {
             this.firstName = firstName;
             }
             public String getLastName() {
             return lastName;
             }
             public void setLastName(String lastName) {
             this.lastName = lastName;
             }
            
            
             public ContactInfo getContactInfo() {
             return contactInfo;
             }
             public void setContactInfo(ContactInfo contactInfo) {
             this.contactInfo = contactInfo;
             }
            
            
             public User() {
             super();
             }
            
            
             @NotNull @Length(min=5, max=15)
             public String getPassword() {
             return password;
             }
             public void setPassword(String password) {
             this.password = password;
             }
            
             @NotNull @Length(min=5, max=15)
             public String getUsername() {
             return username;
             }
             public void setUsername(String username) {
             this.username = username;
             }
             public SellerInfo getSellerInfo() {
             return sellerInfo;
             }
             public void setSellerInfo(SellerInfo sellerInfo) {
             this.sellerInfo = sellerInfo;
             }
            
             @Id @GeneratedValue(strategy=GenerationType.AUTO)
             public Long getId() {
             return id;
             }
             public void setId(Long id) {
             this.id = id;
             }
             public Boolean getSeller() {
             return seller;
             }
             public void setSeller(Boolean seller) {
             this.seller = seller;
             }
            
            
            }
            


            My impression was that @Scope did the binding.

            The remaining code is almost identical to initial example code, but for completness sake.

            <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
            <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
            <%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
            <html>
             <head>
             <title>Register New User</title>
             </head>
             <body>
             <f:view>
             <h:form>
             <table border="0">
             <s:validateAll>
             <tr>
             <td>Username</td>
             <td><h:inputText value="#{user.username}" required="true"/></td>
             </tr>
             <tr>
             <td>First Name</td>
             <td><h:inputText value="#{user.firstName}" required="true"/></td>
             </tr>
             <tr>
             <td>Last Name</td>
             <td><h:inputText value="#{user.lastName}" required="true"/></td>
             </tr>
             <tr>
             <td>Password</td>
             <td><h:inputSecret value="#{user.password}" required="true"/></td>
             </tr>
             </s:validateAll>
             </table>
             <h:messages/>
             <h:commandButton type="submit" value="Register" action="#{register.register}"/>
             </h:form>
             </f:view>
             </body>
            </html>
            


            Finally the RegisterAction, all I changed is the em.persist to em.merge

            import java.util.List;
            
            import javax.ejb.Stateless;
            ...
            import org.jboss.seam.log.Log;
            
            import com.genietown.model.User;
            
            @Stateless
            @Name("register")
            public class RegisterAction implements Register
            {
            
             @In @Valid
             private User user;
            
             @PersistenceContext
             private EntityManager em;
            
             @Logger
             private Log log;
            
             public String register()
             {
             List existing = em.createQuery("select username from User where username=:username")
             .setParameter("username", user.getUsername())
             .getResultList();
             if (existing.size()==0)
             {
             em.merge(user);
             log.info("Registered new user #{user.username}");
             return "/registered.jsp";
             }
             else
             {
             FacesMessages.instance().add("User #{user.username} already exists");
             return null;
             }
             }
            
            }
            


            Thanks for your help. To restate the question, why wouldn't merge simply update the user info instead of creating a new user? Obviously the later is what I want, but I am not sure why this works the way it is setup currently.

            -hc

            • 3. Re: EntityManager merge and the seam-registration example.
              javidjamae

              I know this is a late reply, but...

              Section 3.7 of the hibernate docs tells you that if the id is null (transient object), then it will persist, but if the id is populated (detached object) then it will merge.

              You have a transient object.

              Do you have the app populating the register form with an existing employee, or are you just trying to type in the name of an existing user, hoping that it will update the info? I'm guessing you're trying to do the latter and that's why it isn't doing what you want.

              You'll probably need to add a search screen that finds all the users, allows you to select one (using DataModel/DataModelSelection) then populates the form for you (by using @Out to outject the user). You'll also have to make sure that the search selection starts a conversation (@Begin) and the update feature ends the conversation (@End) because you are spanning multiple requests. Make sure the user object has conversation scope (the default for entity beans, so don't define the @Scope annotation).