1 2 Previous Next 28 Replies Latest reply on Nov 8, 2006 4:05 PM by iradix

    EntityManger-per-user-session...

    bkyrlach

      Okay...

      Everywhere I read, everyone says that this is bad. In fact, it's labeled as an anti-pattern in the Hibernate documentation. However, I still can't help but think that this is an appropriate usage when talking about Seam as the application framework. The only reasons I can find after some searching on Google why this is an anti-pattern is the following...

      1) EntityManager is not thread-safe.

      Rebuttal: em is not thread safe, but the new feature in Seam 1.1.0 of synchronizing calls to SFSB's means that if you wrap the em in a session scoped seam component, then for our purposes it should be threadsafe.

      2) EntityManager uses more memory the longer user session exists because it keeps references to attached objects.

      Rebuttal: This is actually true, but doesn't that make sense for a Seam component. I mean using Hibernate without Seam, you have to reattach your entities all the time, because you're presentation layer is always passing id's instead of objects. With Seams contextual bijection, you're always dealing with objects. So, the old way... (assuming updating an already persistent entity)

      Activity activity = em.find(Activity.class, activityId);
      //update activity
      //activity is persisted at the end of the method call because it's
      //attached from the finder method.

      The new way without using EntityManager-per-user-session...

      @In
      Activity activity;
      ...
      ...
      activity = em.merge(activity);
      //activity is persisted when you called merge, and was already
      //updated because it was injected.

      And the way you would do it with the EntityManager-per-session

      @In
      Activity activity;
      ...
      ...
      //no code nessecary assuming the updated activity was injected
      //and the instance was already attached.

      So, please share your thoughts on this. I need to know why EntityManger-per-session is such a bad idea, or if it does truly make sense when dealing with the idea of a Seam component.

        • 1. Re: EntityManger-per-user-session...
          gavin.king

          If you are really carefully, and really understand the memory management implications and really know what you are doing, you can make this work. But in most cases it is dangerous.

          • 2. Re: EntityManger-per-user-session...
            christian.bauer

            This is a completely backwards way of looking at this. The pattern for Seam is persistence-context-per-transaction or persistence-context-per-conversation. If you want anything else, you don't have to figure out what breaks, but WHY you would want anything else. What are the advantages of persistence-context-per-session?

            • 3. Re: EntityManger-per-user-session...
              bkyrlach

              Gavin...

              Then I guess you're saying for me, this is a really bad idea. :)

              Christian...

              I dunno. I guess to me it just seems somewhat backwards to have to always be merging your entities and/or doing a query before every update. Especially when the documentation for merge suggests that it's mostly used to re-attach instances that have been serialized/deserialized.

              Not that I'm right. In fact, if you ask Gavin, I'm usually wrong. However, I just couldn't find anyone who would tell me why this is a bad thing, and I figured since Gavin did Seam and Hibernate, he'd be the man in the know.

              Thanks for your responses though. I'm gathering that this is a really bad thing to do, which is at least some of what I was hoping to find out.

              • 4. Re: EntityManger-per-user-session...
                christian.bauer

                You want persistence-context-per-conversation.

                • 5. Re: EntityManger-per-user-session...
                  bkyrlach

                  Not really. I'm using that now, and I'm still ending up doing merging and funky stuff to make everything "Just Work"(tm)(c)(pp).

                  Here's some (poorly written, tbh) code to illustrate...

                  I have a user...

                  @Entity
                  @Name("user")
                  @Scope(ScopeType.SESSION)
                  @Role(name="newUser", scope=ScopeType.EVENT)
                  public class User implements Serializable
                  {
                  private Long id;
                  private Long version;
                  private Long index;
                  private String username;
                  private String password;
                  private List activities;
                  private Team team;

                  public User(Long id, Long version, Long index, String username, String password, List activities, Team team)
                  {
                  this.id = id;
                  this.version = version;
                  this.index = index;
                  this.username = username;
                  this.password = password;
                  this.activities = activities;
                  this.team = team;
                  }

                  public User() {}

                  @Id @GeneratedValue
                  public Long getId()
                  {
                  return id;
                  }
                  public void setId(Long id)
                  {
                  this.id = id;
                  }

                  @Version
                  public Long getVersion()
                  {
                  return version;
                  }
                  public void setVersion(Long version)
                  {
                  this.version = version;
                  }

                  public Long getIndex()
                  {
                  return index;
                  }
                  public void setIndex(Long index)
                  {
                  this.index = index;
                  }

                  @OneToMany(fetch=FetchType.EAGER, mappedBy="user")
                  @OrderBy("name")
                  @IndexColumn(name="id")
                  public List getActivities()
                  {
                  return activities;
                  }
                  public void setActivities(List activities)
                  {
                  this.activities = activities;
                  }

                  public String getPassword()
                  {
                  return password;
                  }
                  public void setPassword(String password)
                  {
                  this.password = password;
                  }

                  public String getUsername()
                  {
                  return username;
                  }
                  public void setUsername(String username)
                  {
                  this.username = username;
                  }

                  @ManyToOne
                  public Team getTeam()
                  {
                  return team;
                  }
                  public void setTeam(Team team)
                  {
                  this.team = team;
                  }

                  public String toString()
                  {
                  return this.getClass().getName() + "@" + (id == null ? "null" : id);
                  }

                  public boolean equals(Object o)
                  {
                  if(o.getClass().equals(this.getClass()))
                  {
                  return (o.toString().equals(this.toString()));
                  }
                  return false;
                  }

                  public int hashCode()
                  {
                  return this.toString().hashCode();
                  }
                  }

                  And an activity...

                  @Entity
                  @Name("activity")
                  @Scope(ScopeType.CONVERSATION)
                  @CausesRefresh(objectsToRefresh="user")
                  public class Activity implements Serializable
                  {
                  private Long id;
                  private String name;
                  private User user;
                  private Date date;
                  private Long version;
                  private Long index;

                  public Activity(Long id, Long index, String name, User user, Date date, Long version)
                  {
                  // TODO Auto-generated constructor stub
                  this.name = name;
                  this.user = user;
                  this.date = date;
                  this.version = version;
                  }

                  public Activity() {}

                  @Id @GeneratedValue
                  public Long getId()
                  {
                  return id;
                  }
                  public void setId(Long id)
                  {
                  this.id = id;
                  }

                  public Long getIndex()
                  {
                  return index;
                  }
                  public void setIndex(Long index)
                  {
                  this.index = index;
                  }

                  public Date getDate()
                  {
                  return date;
                  }
                  public void setDate(Date date)
                  {
                  this.date = date;
                  }

                  public String getName()
                  {
                  return name;
                  }
                  public void setName(String name)
                  {
                  this.name = name;
                  }

                  @ManyToOne
                  public User getUser()
                  {
                  return user;
                  }
                  public void setUser(User user)
                  {
                  this.user = user;
                  }

                  @Version
                  public Long getVersion()
                  {
                  return version;
                  }
                  public void setVersion(Long version)
                  {
                  this.version = version;
                  }

                  public String toString()
                  {
                  return this.getClass().getName() + "@" + (id == null ? "null" : id) + "[" + "date" + "=" + (date == null ? "null" : date.toString()) + "]";
                  }

                  public boolean equals(Object o)
                  {
                  if(o.getClass().equals(this.getClass()))
                  {
                  return (o.toString().equals(this.toString()));
                  }
                  return false;
                  }

                  public int hashCode()
                  {
                  return this.toString().hashCode();
                  }
                  }

                  Now, when I go to edit an activity I have to do the following (this is with pc-per-conversation)...

                  List userActivities = user.getActivities();
                  for(Activity a: userActivities)
                  {
                  if(a.getId().equals(activity.getId()))
                  {
                  userActivities.set(userActivities.indexOf(a), activity);
                  }
                  }
                  user.setActivities(userActivities);

                  Create is even worse, since I have to make sure I'm inserting the activity into the user's activities collection as per the ordering specified by the annotations on the get/setActivities...

                  Now, a second way to do it is to...

                  public String preEditActivity()
                  {
                  ...
                  ...
                  ...
                  em.merge(user);
                  ...
                  ...
                  ...
                  }

                  and in save...

                  public String saveActivity()
                  {
                  ...
                  ...
                  ...
                  em.flush();
                  em.refresh(user);
                  }

                  Which also seems unessecarily ugly. That's because the user is getting pulled from the database in the UserActions SLSB, so the extended PC for ActivityActions SFSB (conversation scoped) doesn't contain an attached reference for the user bean. Even the merge/refresh wayof doing it in the second psuedo-code set is still kind of ugly... especially if the EM decides to do a full database query. Imagine if user.getActivities() had 3000 elements in it.

                  Sorry for the rambling. I'm just trying to understand.

                  • 6. Re: EntityManger-per-user-session...
                    christian.bauer

                    I can't read that code.

                    • 7. Re: EntityManger-per-user-session...
                      bkyrlach

                      (reposted using an updated understanding of code-blocks... :(

                      Not really. I'm using that now, and I'm still ending up doing merging and funky stuff to make everything "Just Work"(tm)(c)(pp).

                      Here's some (poorly written, tbh) code to illustrate...

                      I have a user...

                      package sample.model;
                      
                      import java.io.Serializable;
                      import java.util.List;
                      
                      import javax.persistence.Entity;
                      import javax.persistence.FetchType;
                      import javax.persistence.GeneratedValue;
                      import javax.persistence.Id;
                      import javax.persistence.ManyToOne;
                      import javax.persistence.OneToMany;
                      import javax.persistence.OrderBy;
                      import javax.persistence.Version;
                      
                      import org.hibernate.annotations.IndexColumn;
                      import org.jboss.seam.ScopeType;
                      import org.jboss.seam.annotations.Name;
                      import org.jboss.seam.annotations.Role;
                      import org.jboss.seam.annotations.Scope;
                      
                      @Entity
                      @Name("user")
                      @Scope(ScopeType.SESSION)
                      @Role(name="newUser", scope=ScopeType.EVENT)
                      public class User implements Serializable
                      {
                       private Long id;
                       private Long version;
                       private Long index;
                       private String username;
                       private String password;
                       private List<Activity> activities;
                       private Team team;
                      
                       public User(Long id, Long version, Long index, String username, String password, List<Activity> activities, Team team)
                       {
                       this.id = id;
                       this.version = version;
                       this.index = index;
                       this.username = username;
                       this.password = password;
                       this.activities = activities;
                       this.team = team;
                       }
                      
                       public User() {}
                      
                       @Id @GeneratedValue
                       public Long getId()
                       {
                       return id;
                       }
                       public void setId(Long id)
                       {
                       this.id = id;
                       }
                      
                       @Version
                       public Long getVersion()
                       {
                       return version;
                       }
                       public void setVersion(Long version)
                       {
                       this.version = version;
                       }
                      
                       public Long getIndex()
                       {
                       return index;
                       }
                       public void setIndex(Long index)
                       {
                       this.index = index;
                       }
                      
                       @OneToMany(fetch=FetchType.EAGER, mappedBy="user")
                       @OrderBy("name")
                       @IndexColumn(name="id")
                       public List<Activity> getActivities()
                       {
                       return activities;
                       }
                       public void setActivities(List<Activity> activities)
                       {
                       this.activities = activities;
                       }
                      
                       public String getPassword()
                       {
                       return password;
                       }
                       public void setPassword(String password)
                       {
                       this.password = password;
                       }
                      
                       public String getUsername()
                       {
                       return username;
                       }
                       public void setUsername(String username)
                       {
                       this.username = username;
                       }
                      
                       @ManyToOne
                       public Team getTeam()
                       {
                       return team;
                       }
                       public void setTeam(Team team)
                       {
                       this.team = team;
                       }
                      
                       public String toString()
                       {
                       return this.getClass().getName() + "@" + (id == null ? "null" : id);
                       }
                      
                       public boolean equals(Object o)
                       {
                       if(o.getClass().equals(this.getClass()))
                       {
                       return (o.toString().equals(this.toString()));
                       }
                       return false;
                       }
                      
                       public int hashCode()
                       {
                       return this.toString().hashCode();
                       }
                      }
                      

                      And an activity...

                      package sample.model;
                      
                      import java.io.Serializable;
                      import java.util.Date;
                      
                      import javax.persistence.Entity;
                      import javax.persistence.GeneratedValue;
                      import javax.persistence.Id;
                      import javax.persistence.JoinColumn;
                      import javax.persistence.ManyToOne;
                      import javax.persistence.Version;
                      
                      import org.jboss.seam.ScopeType;
                      import org.jboss.seam.annotations.Name;
                      import org.jboss.seam.annotations.Role;
                      import org.jboss.seam.annotations.Scope;
                      
                      import sample.general.CausesRefresh;
                      
                      @Entity
                      @Name("activity")
                      @Scope(ScopeType.CONVERSATION)
                      @CausesRefresh(objectsToRefresh="user")
                      public class Activity implements Serializable
                      {
                       private Long id;
                       private String name;
                       private User user;
                       private Date date;
                       private Long version;
                       private Long index;
                      
                       public Activity(Long id, Long index, String name, User user, Date date, Long version)
                       {
                       // TODO Auto-generated constructor stub
                       this.name = name;
                       this.user = user;
                       this.date = date;
                       this.version = version;
                       }
                      
                       public Activity() {}
                      
                       @Id @GeneratedValue
                       public Long getId()
                       {
                       return id;
                       }
                       public void setId(Long id)
                       {
                       this.id = id;
                       }
                      
                       public Long getIndex()
                       {
                       return index;
                       }
                       public void setIndex(Long index)
                       {
                       this.index = index;
                       }
                      
                       public Date getDate()
                       {
                       return date;
                       }
                       public void setDate(Date date)
                       {
                       this.date = date;
                       }
                      
                       public String getName()
                       {
                       return name;
                       }
                       public void setName(String name)
                       {
                       this.name = name;
                       }
                      
                       @ManyToOne
                       public User getUser()
                       {
                       return user;
                       }
                       public void setUser(User user)
                       {
                       this.user = user;
                       }
                      
                       @Version
                       public Long getVersion()
                       {
                       return version;
                       }
                       public void setVersion(Long version)
                       {
                       this.version = version;
                       }
                      
                       public String toString()
                       {
                       return this.getClass().getName() + "@" + (id == null ? "null" : id) + "[" + "date" + "=" + (date == null ? "null" : date.toString()) + "]";
                       }
                      
                       public boolean equals(Object o)
                       {
                       if(o.getClass().equals(this.getClass()))
                       {
                       return (o.toString().equals(this.toString()));
                       }
                       return false;
                       }
                      
                       public int hashCode()
                       {
                       return this.toString().hashCode();
                       }
                      }
                      


                      Now, when I go to edit an activity I have to do the following (this is with pc-per-conversation)...

                       List userActivities = user.getActivities();
                       for(Activity a: userActivities)
                       {
                       if(a.getId().equals(activity.getId()))
                       {
                       userActivities.set(userActivities.indexOf(a), activity);
                       }
                       }
                       user.setActivities(userActivities);
                      


                      Create is even worse, since I have to make sure I'm inserting the activity into the user's activities collection as per the ordering specified by the annotations on the get/setActivities...

                      Now, a second way to do it is to...

                       public String preEditActivity()
                       {
                       ...
                       ...
                       ...
                       em.merge(user);
                       ...
                       ...
                       ...
                       }
                      


                      and in save...

                       public String saveActivity()
                       {
                       ...
                       ...
                       ...
                       em.flush();
                       em.refresh(user);
                       }
                      


                      Which also seems unessecarily ugly. That's because the user is getting pulled from the database in the UserActions SLSB, so the extended PC for ActivityActions SFSB (conversation scoped) doesn't contain an attached reference for the user bean. Even the merge/refresh wayof doing it in the second psuedo-code set is still kind of ugly... especially if the EM decides to do a full database query. Imagine if user.getActivities() had 3000 elements in it.

                      Sorry for the rambling. I'm just trying to understand.

                      • 8. Re: EntityManger-per-user-session...
                        iradix

                        With a proper cascade on the activities list stored within the user, you should be able to just bind the values that are editable via the value attribute of each JSF component. After they are validated and the model is updated (i.e. new values are set on each appropriate activity) saving the user at the end of the transaction will update the DB representation of whatever activities have changed. There should be no merging necessary as long as your conversation is long running because they are the same objects from page to page. What could be easier than that?

                        If what you're saying about the UserActions SLSB is that you have a User spanning more than one conversation, try this:

                        @In(create = true)
                        private EntityManager em;
                        
                        private String userId;
                        
                        public void setUser(User user){
                         this.userId = user.getId();
                        }
                        
                        public User getUser(){
                         return em.find(User.class, userId);
                        }
                        


                        Seam will make sure the appropriate conversation scoped em is injected into your SLSB on each call so you'll get the same User object every time, as long as you are within the same conversation. No merging necessary. You can even inject it into other beans using @In(value="#{userActions.user}")

                        • 9. Re: EntityManger-per-user-session...
                          bkyrlach

                          Before I argue with you I want to point out that I like your solution. My argument is just me trying to understand more of what you're saying.

                          "iradix" wrote:
                          With a proper cascade on the activities list stored within the user, you should be able to just bind the values that are editable via the value attribute of each JSF component.


                          I don't understand this part at all. I know that if I stick the edited activity back into the users collection of activites and have the correct cascade type, that when I save the updated user, it will save the updated activity as well. However, this has two problems with it.

                          #1: Iterating through the users activities to find the one who's id matches the activity that's being edited is painful and can consume a lot of resources. If the users collection of activities is 3000 elements big, for example, I'd imagine that this method would take a long time to return.

                          #2: The activity that's being injected doesn't come from the collection of the users activities. This is from following the booking example, where you have to have a list marked as @DataModel in order to have s:link be able to pull out individual activities to edit. Here's an example to illustrate...

                           @DataModel
                           List<Activity> activities;
                          
                           public void getActivitiesForUser()
                           {
                           activities = em.createQuery("from Activity a where a.user = :user order by a.name").setParameter("user", user).getResultList();
                           }
                          


                          and in the view...

                           <h:dataTable value="#{activities}" var="tmp">
                           <h:column>
                           <f:facet name="header">Name</f:facet>
                           #{tmp.name}
                           </h:column>
                           <h:column>
                           <f:facet name="header">Date</f:facet>
                           #{tmp.date}
                           </h:column>
                           <h:column>
                           <f:facet name="header">Edit</f:facet>
                           <s:link value="Edit Activity" action="#{editActivity.editActivity(tmp)}"/>
                           </h:column>
                           </h:dataTable>
                          


                          "iradix" wrote:
                          After they are validated and the model is updated (i.e. new values are set on each appropriate activity) saving the user at the end of the transaction will update the DB representation of whatever activities have changed. There should be no merging necessary as long as your conversation is long running because they are the same objects from page to page. What could be easier than that?


                          That's assuming that the user is being managed by the conversations entitymanager. Otherwise, I have to merge user at the start of the editActivity. Again, more code...

                           @In(required=false)
                           User user;
                          
                           @Begin
                           public String preEditActivity()
                           {
                           user = em.merge(user);
                           this.activity = tmp;
                           return "/activity/edit.xhtml";
                           }
                          


                          "iradix" wrote:
                          If what you're saying about the UserActions SLSB is that you have a User spanning more than one conversation, try this:

                          @In(create = true)
                          private EntityManager em;
                          
                          private String userId;
                          
                          public void setUser(User user){
                           this.userId = user.getId();
                          }
                          
                          public User getUser(){
                           return em.find(User.class, userId);
                          }
                          



                          I guess that's what I'm saying. I'm not sure I'd call it spanning more than one conversation. In reality, user is a session scoped bean representing the currently logged in user. In user actions, the user logs in thusly...

                           @In(required=false)
                           @Out(required=false)
                           private User user;
                          
                           @In(required=false)
                           private User newUser;
                          
                           public String login()
                           {
                           User temp = (User)manager.createQuery(
                           "from User u where u.username = :username and u.password = :password").setParameter(
                           "username", user.getUsername()).setParameter("password", user.getPassword()).getSingleResult();
                           if (temp != null)
                           {
                           user=temp;
                           return "/activity/viewer.xhtml";
                           }
                           facesMessages.add("This user does not exist in our database.");
                           return Outcome.REDISPLAY;
                           }
                          


                          Because this happens in userActions, activityActions extended entitymanager says that the user I inject from the session context is not attached. Of course, it is correct, because that entitymanager wasn't the one that pulled the user from the database.

                          "iradix" wrote:
                          Seam will make sure the appropriate conversation scoped em is injected into your SLSB on each call so you'll get the same User object every time, as long as you are within the same conversation. No merging necessary. You can even inject it into other beans using @In(value="#{userActions.user}")


                          I don't understand... how does it know which persistence unit that this EM should be tied to? What if in my conversation scoped activityActions I inject two persistenceContexts thusly...

                           @PersistenceContext(unitName="db1")
                           EntityManager manager1;
                          
                           @PersistenceContext(unitName="db2")
                           EntityManager manager2;
                          


                          Which one would be injected? I'm confused.

                          • 10. Re: EntityManger-per-user-session...
                            iradix

                            I think you're biggest problem is you haven't gotten your head wrapped around the conversational model yet. It definitely takes time and I'd recommend that the first thing you do is at least skim through the Seam reference documentation in it's entirety.

                            That being said, I'll try to explain as best I can.

                            I don't understand this part at all. I know that if I stick the edited activity back into the users collection of activites and have the correct cascade type, that when I save the updated user, it will save the updated activity as well.


                            Where you're going wrong here is you don't need to stick the activity back into the list if it's already there. If you are using a conversationally scoped EM (which is what my suggestions are based on) then there will only be one version of each activity per conversation, whether that activity was retrieved through user.getActivities or through SELECT..... That's how an EntityManager works and it's important to understand that. So rather than the view that you provided, what I'm talking about is the edit view where you will have values bound like:

                            Name: <h:inputText value="#{selectedActivity.name}"/>
                            


                            When the update data model phase happens the name would be updated on the one and only version of the selected Activity, which is managed by your conversation scoped EM, and when the transaction is commited it will be updated. After that anytime you access user.getActivities() you will see the updated activity and it will be reflected in the database. Does that make more sense?

                            I guess that's what I'm saying. I'm not sure I'd call it spanning more than one conversation. In reality, user is a session scoped bean representing the currently logged in user.


                            Well, since a session may contain many conversations, then your User object at least has the capability of spanning more than one and that should be factored in. Again, this seems to be an issue with your understanding of how conversations, and more specifically conversation scoped EMs work. You cannot use the @PersistenceContext annotation to specify a conversation scoped EM. It needs to be injected using the @In annotation, and if you've configured that correctly (see the docs on how) Seam will magically make sure that the same EM gets injected into every bean within the same conversation.


                            As far as persistence units are concerned, I've never had the need to deal with more than one on a project, but I'd imagine if I did I could create 2 Seam managed EMs, one for each persistence context and then your example would become:


                            @In(create="true")
                            EntityManager manager1;
                            
                            @In(create="true")
                            EntityManager manager2;
                            


                            Where manager1 and manager2 are both properly configured, conversation scoped EMs.

                            Make more sense?


                            • 11. Re: EntityManger-per-user-session...
                              pmuir

                               

                              "iradix" wrote:
                              Well, since a session may contain many conversations, then your User object at least has the capability of spanning more than one and that should be factored in.


                              I think an EntityHome might work well for an entity which must be session scoped.

                              • 12. Re: EntityManger-per-user-session...
                                bkyrlach

                                 

                                "iradix" wrote:
                                I think you're biggest problem is you haven't gotten your head wrapped around the conversational model yet. It definitely takes time and I'd recommend that the first thing you do is at least skim through the Seam reference documentation in it's entirety.


                                I've read the documentation in it's entirety at least 20-30 times now.

                                "iradix" wrote:
                                That being said, I'll try to explain as best I can.

                                I don't understand this part at all. I know that if I stick the edited activity back into the users collection of activites and have the correct cascade type, that when I save the updated user, it will save the updated activity as well.


                                Where you're going wrong here is you don't need to stick the activity back into the list if it's already there.


                                Hibernate documentation says specifically that I'm responsable for managing both sides of a bi-directional relationship. I've never experienced the behavior you're talking about, and even though I'm injecting my EntityManager through the @PersistenceContext, I'm pretty sure it's still conversationally scoped becuase I specififed it in the following way...

                                @PersistenceContext(type=PersistenceContextType.EXTENDED, unitName="testDatabase")
                                EntityManager em;
                                


                                "iradix" wrote:
                                If you are using a conversationally scoped EM (which is what my suggestions are based on) then there will only be one version of each activity per conversation, whether that activity was retrieved through user.getActivities or through SELECT..... That's how an EntityManager works and it's important to understand that.


                                This is true only if that conversationally scoped EM has the User bean as an attached instance. Otherwise, it will most definitely not recognize that the detached entity comming from the users activities collection is the same as the attached entity being injected from the conversation scope.

                                "iradix" wrote:
                                So rather than the view that you provided, what I'm talking about is the edit view where you will have values bound like:

                                Name: <h:inputText value="#{selectedActivity.name}"/>
                                


                                When the update data model phase happens the name would be updated on the one and only version of the selected Activity, which is managed by your conversation scoped EM, and when the transaction is commited it will be updated. After that anytime you access user.getActivities() you will see the updated activity and it will be reflected in the database. Does that make more sense?


                                That does not happen in my code, and my EM is conversation scoped because it is an extended persistence context managed by a conversationally scoped SFSB. (do you still call them session beans if they're not session scoped... lol)

                                "iradix" wrote:
                                I guess that's what I'm saying. I'm not sure I'd call it spanning more than one conversation. In reality, user is a session scoped bean representing the currently logged in user.


                                Well, since a session may contain many conversations, then your User object at least has the capability of spanning more than one and that should be factored in. Again, this seems to be an issue with your understanding of how conversations, and more specifically conversation scoped EMs work. You cannot use the @PersistenceContext annotation to specify a conversation scoped EM.


                                Okay, I need to read more carefully. You're saying that even though the owner of this EM is conversationally scoped, and the PersistenceContextType is extended, that it's not a conversationally scoped EM? I thought the whole point of setting it to extended was that Seam garuntee's me that the same EM will be available across multiple method calls to the same object (in this case, ActivityActions). In fact, both the DVDStore and the Booking app don't use an EM set up through components.xml and injected using the @In annotation.

                                "iradix" wrote:
                                It needs to be injected using the @In annotation, and if you've configured that correctly (see the docs on how) Seam will magically make sure that the same EM gets injected into every bean within the same conversation.


                                Okay, I'm completly re-working my demo app to use what you've specified. Somehow, I have a feeling we're still not communicating the whole picture to eachother, but I'm willing to learn. :)

                                "iradix" wrote:
                                As far as persistence units are concerned, I've never had the need to deal with more than one on a project, but I'd imagine if I did I could create 2 Seam managed EMs, one for each persistence context and then your example would become:


                                @In(create="true")
                                EntityManager manager1;
                                
                                @In(create="true")
                                EntityManager manager2;
                                


                                Where manager1 and manager2 are both properly configured, conversation scoped EMs.


                                I didn't at first realize you were talking about configuring EM's through the use of components.xml. In that case, it's perfectly trivial to inject multiple EntityManagers tied to different persistence contexts.

                                "iradix" wrote:

                                Make more sense?


                                No, but I'm working on some demo code to see if I can begin to understand. Also, even though I'm grateful for your help, this has also seemed to stray a long way from what I was originally asking, which is... why is it so bad to just have a Session scoped EM. However, I'd be much more interested in getting it to work the "correct way" than to find out why the wrong way is wrong.

                                /sigh

                                P.s. You say that I never have to merge, but in reality, em.find(User.class, userId) is doing the same thing (not the same operation, but I mean what it's doing is querying the database and reattaching my detached user). The whole point of my question is, why do I have to always worry about my session scoped objects becomming detached because the scope of my persistence context < scope of my seam component. I don't care if it's em.find() or em.merge(), both seem kind of silly to have to do each time. It seems to me that it should be entity managers job to keep track of this for me, rather than me having to worry in every business method on every action to ensure that all of my objects are attached to the current entity manager.

                                Especially when you think of a user with a large collection of activities. Then you can imagine that just editing one activity requires a database trip that could bring back 3-4k records or more. I mean, that's not going to be the case here, of course, but I'm just wondering why it needs to be this way.

                                Don't get me wrong, I'm reading what you've posted and I'm even reworking my example to see if I can make some sense of what you're saying. I'm always trying to learn more.

                                • 13. Re: EntityManger-per-user-session...
                                  iradix

                                  It does seem that we aren't quite understanding each other. Let me clarify a few points that I think might help you.

                                  1) So I don't confuse things (including myself) I'm going to pretend that Persistence Context and Entity Manager mean the same thing. @PersistenceContext(type=EXTENDED) does not provide an EM scoped to the conversation, because it is not aware that conversations exist. This confused me at first too because there is a lot of new information to take in. First you have the EJB3 spec and the EM propagation rules there, and then you have your Seam Managed EM with it's own rules. What you get by using @PersistenceContext(type=EXTENDED) is an EM that will survive from call to call on the session bean where it's defined, even if those calls span requests. It will also be propagated through to other session beans called by the original. That might be an oversimplification but I think you get the gist. What you get with a seam managed persistence context is a single EM per conversation which can be injected using @In.

                                  2) Once you've got your Seam Managed EM set up it will act as an object cache over the course of the conversation, only hitting the database for object retrieval when it has to. So if you retrieve a User as soon as the conversation is started, every time you call em.find(User.class, userId) afterwards you get the cached user back. No extraneous queries. The EM will also check to see which objects have been updated and generated the appropriate sql once it is flushed.

                                  3) I see 2 reasons off the top of my head not to use a session scoped EM. The first is that you are caching data that the user has accessed throughout their session when much of it is quite possibly no longer relevant. Why take up the extra memory? I see no real benefits to this approach and even if your app is small enough that it doesn't make a difference now, it's not going to be very scalable. The second is a matter of providing accurate data. In my opinion it's a good thing to drop your entity manager at a well defined point (when the data in it is no longer being used) so that if the user chooses another operation that relies on that data a fresh, up to date version of it will be retrieved. If you use the same entity manager throughout a user's session they will only see any changes that they have made (which are retrieved from their EM cache).

                                  • 14. Re: EntityManger-per-user-session...
                                    bkyrlach

                                    Okay, that makes a lot more sense to me. However, things still don't seem to be working quite the way you describe. Here's my newly reworked code, using two generic beans, Parent and Child.

                                    Parent bean...

                                    package sample.model.pc;
                                    
                                    import java.util.List;
                                    
                                    import javax.persistence.CascadeType;
                                    import javax.persistence.Entity;
                                    import javax.persistence.FetchType;
                                    import javax.persistence.GeneratedValue;
                                    import javax.persistence.Id;
                                    import javax.persistence.OneToMany;
                                    import javax.persistence.Version;
                                    
                                    import org.jboss.seam.ScopeType;
                                    import org.jboss.seam.annotations.Name;
                                    import org.jboss.seam.annotations.Role;
                                    import org.jboss.seam.annotations.Scope;
                                    
                                    @Entity
                                    @Name("parent")
                                    @Scope(ScopeType.SESSION)
                                    @Role(name="newParent", scope=ScopeType.EVENT)
                                    public class Parent
                                    {
                                     Long id;
                                     Long version;
                                     String name;
                                     List<Child> children;
                                    
                                     public Parent(Long id, Long version, String name, List<Child> children)
                                     {
                                     // TODO Auto-generated constructor stub
                                     this.id = id;
                                     this.version = version;
                                     this.name = name;
                                     this.children = children;
                                     }
                                    
                                     public Parent() {}
                                    
                                     @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="parent")
                                     public List<Child> getChildren()
                                     {
                                     return children;
                                     }
                                     public void setChildren(List<Child> children)
                                     {
                                     this.children = children;
                                     }
                                    
                                     @Id @GeneratedValue
                                     public Long getId()
                                     {
                                     return id;
                                     }
                                     public void setId(Long id)
                                     {
                                     this.id = id;
                                     }
                                    
                                     public String getName()
                                     {
                                     return name;
                                     }
                                     public void setName(String name)
                                     {
                                     this.name = name;
                                     }
                                    
                                     @Version
                                     public Long getVersion()
                                     {
                                     return version;
                                     }
                                     public void setVersion(Long version)
                                     {
                                     this.version = version;
                                     }
                                    
                                     public boolean equals(Object o)
                                     {
                                     if(o instanceof Parent)
                                     {
                                     return o.toString().equals(this.toString());
                                     }
                                     return false;
                                     }
                                    
                                     public String toString()
                                     {
                                     return this.getClass() + "@" + name;
                                     }
                                    
                                     public int hashCode()
                                     {
                                     return this.toString().hashCode();
                                     }
                                    }
                                    


                                    Child bean...

                                    package sample.model.pc;
                                    
                                    import javax.persistence.Entity;
                                    import javax.persistence.GeneratedValue;
                                    import javax.persistence.Id;
                                    import javax.persistence.ManyToOne;
                                    import javax.persistence.Version;
                                    
                                    import org.jboss.seam.ScopeType;
                                    import org.jboss.seam.annotations.Name;
                                    import org.jboss.seam.annotations.Scope;
                                    
                                    @Entity
                                    @Name("child")
                                    @Scope(ScopeType.CONVERSATION)
                                    public class Child
                                    {
                                     Long id;
                                     Long version;
                                     String name;
                                     Parent parent;
                                    
                                     public Child(Long id, Long version, String name, Parent parent)
                                     {
                                     // TODO Auto-generated constructor stub
                                     this.id = id;
                                     this.version = version;
                                     this.name = name;
                                     this.parent = parent;
                                     }
                                    
                                     public Child() {}
                                    
                                     @Id @GeneratedValue
                                     public Long getId()
                                     {
                                     return id;
                                     }
                                     public void setId(Long id)
                                     {
                                     this.id = id;
                                     }
                                    
                                     public String getName()
                                     {
                                     return name;
                                     }
                                     public void setName(String name)
                                     {
                                     this.name = name;
                                     }
                                    
                                     @ManyToOne
                                     public Parent getParent()
                                     {
                                     return parent;
                                     }
                                     public void setParent(Parent parent)
                                     {
                                     this.parent = parent;
                                     }
                                    
                                     @Version
                                     public Long getVersion()
                                     {
                                     return version;
                                     }
                                     public void setVersion(Long version)
                                     {
                                     this.version = version;
                                     }
                                    
                                     public boolean equals(Object o)
                                     {
                                     if(o instanceof Child)
                                     {
                                     return o.toString().equals(this.toString());
                                     }
                                     return false;
                                     }
                                    
                                     public String toString()
                                     {
                                     return this.getClass() + "@" + name;
                                     }
                                    
                                     public int hashCode()
                                     {
                                     return this.toString().hashCode();
                                     }
                                    }
                                    


                                    Persistence.xml...

                                    <persistence>
                                     <persistence-unit name="testDatabase">
                                     <provider>org.hibernate.ejb.HibernatePersistence</provider>
                                     <jta-data-source>java:/DefaultDS</jta-data-source>
                                     <properties>
                                     <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
                                     <property name="hibernate.show_sql" value="true"/>
                                     <!-- These are the default for JBoss EJB3, but not for HEM: -->
                                     <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
                                     <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
                                     <property name="jboss.entity.manager.factory.jndi.name" value="java:/testEntityManagerFactory"/>
                                     </properties>
                                     </persistence-unit>
                                     <persistence-unit name="schoolDatabase">
                                     <provider>org.hibernate.ejb.HibernatePersistence</provider>
                                     <jta-data-source>java:/jdbc/OracleDS</jta-data-source>
                                     <properties>
                                     <property name="hibernate.hbm2ddl.auto" value="none"/>
                                     <property name="hibernate.show_sql" value="true"/>
                                     <!-- These are the default for JBoss EJB3, but not for HEM: -->
                                     <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
                                     <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
                                     <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>
                                     </properties>
                                     </persistence-unit>
                                    </persistence>
                                    


                                    Components.xml...

                                    <?xml version="1.0" encoding="UTF-8"?>
                                    <components>
                                     <component class="org.jboss.seam.core.Jbpm">
                                     <property name="pageflowDefinitions">pageflow.jpdl.xml</property>
                                     </component>
                                     <component name="em" class="org.jboss.seam.core.ManagedPersistenceContext">
                                     <property name="persistenceUnitJndiName">java:/testEntityManagerFactory</property>
                                     </component>
                                    </components>
                                    


                                    ParentActions...

                                    package sample.business.pc;
                                    
                                    import javax.ejb.Stateless;
                                    import javax.interceptor.Interceptors;
                                    import javax.persistence.EntityManager;
                                    
                                    import org.jboss.seam.annotations.In;
                                    import org.jboss.seam.annotations.Name;
                                    import org.jboss.seam.annotations.Out;
                                    import org.jboss.seam.contexts.Contexts;
                                    import org.jboss.seam.ejb.SeamInterceptor;
                                    
                                    import sample.model.pc.Parent;
                                    
                                    @Stateless
                                    @Name("parentActions")
                                    @Interceptors(SeamInterceptor.class)
                                    public class ParentActionsImpl implements ParentActions
                                    {
                                     @In(create=true)
                                     EntityManager em;
                                    
                                     @Out(required=false)
                                     Parent parent;
                                    
                                     @In(required=false)
                                     Parent newParent;
                                    
                                     public String createParent()
                                     {
                                     em.persist(newParent);
                                     parent = newParent;
                                     return "/pc/viewer.xhtml";
                                     }
                                    
                                     public Parent getParent(Long id)
                                     {
                                     return em.find(Parent.class, id);
                                     }
                                    }
                                    


                                    ChildListBean...

                                    package sample.business.pc;
                                    
                                    import java.util.List;
                                    
                                    import javax.ejb.Remove;
                                    import javax.ejb.Stateful;
                                    import javax.persistence.EntityManager;
                                    
                                    import org.jboss.seam.ScopeType;
                                    import org.jboss.seam.annotations.Destroy;
                                    import org.jboss.seam.annotations.In;
                                    import org.jboss.seam.annotations.Name;
                                    import org.jboss.seam.annotations.Scope;
                                    import org.jboss.seam.annotations.datamodel.DataModel;
                                    import org.jboss.seam.contexts.Contexts;
                                    
                                    import sample.model.pc.Child;
                                    import sample.model.pc.Parent;
                                    
                                    @Stateful
                                    @Name("childListBean")
                                    @Scope(ScopeType.SESSION)
                                    public class ChildListBeanImpl implements ChildListBean
                                    {
                                     @In(create=true)
                                     EntityManager em;
                                    
                                     @In
                                     Parent parent;
                                    
                                     @DataModel
                                     List<Child> children;
                                    
                                     public void queryForChildrenForParent()
                                     {
                                     System.out.println(parent);
                                     System.out.println(em);
                                     if(parent != null)
                                     {
                                     children = em.createQuery("from Child c where c.parent = :parent").setParameter("parent", parent).getResultList();
                                     }
                                     }
                                    
                                     @Remove @Destroy
                                     public void destroy()
                                     {
                                    
                                     }
                                    }
                                    


                                    Pages.xml...

                                    <pages>
                                     <page view-id="/activity/viewer.xhtml" action="#{activityActions.getActivitiesForUser}"/>
                                     <page view-id="/pc/viewer.xhtml" action="#{childListBean.queryForChildrenForParent}"/>
                                    </pages>
                                    


                                    createParent.xhtml

                                    <html xmlns="http://www.w3.org/1999/xhtml"
                                     xmlns:f="http://java.sun.com/jsf/core"
                                     xmlns:h="http://java.sun.com/jsf/html"
                                     xmlns:ui="http://java.sun.com/jsf/facelets"
                                     xmlns:c="http://java.sun.com/jstl/core">
                                     <head>
                                     <title>Create Parent</title>
                                     </head>
                                     <body>
                                     <h:form>
                                     <table>
                                     <tr>
                                     <td>Name:</td>
                                     <td><h:inputText value="#{newParent.name}"/></td>
                                     </tr>
                                     <tr>
                                     <td>Submit:</td>
                                     <td><h:commandButton action="#{parentActions.createParent}"/></td>
                                     </tr>
                                     </table>
                                     </h:form>
                                     </body>
                                    </html>
                                    


                                    viewer.xhtml

                                    <html xmlns="http://www.w3.org/1999/xhtml"
                                     xmlns:f="http://java.sun.com/jsf/core"
                                     xmlns:h="http://java.sun.com/jsf/html"
                                     xmlns:ui="http://java.sun.com/jsf/facelets"
                                     xmlns:c="http://java.sun.com/jstl/core"
                                     xmlns:s="http://jboss.com/products/seam/taglib"
                                     xmlns:a="https://ajax4jsf.dev.java.net/ajax">
                                     <head>
                                     <title>Create Parent</title>
                                     </head>
                                     <body>
                                     <h:dataTable value="#{children}" var="tmp" rendered="#{!empty children}">
                                     <h:column>
                                     <f:facet name="header">Name</f:facet>
                                     #{tmp.name}
                                     </h:column>
                                     <h:column>
                                     <f:facet name="header">Edit</f:facet>
                                     <s:link value="Edit Child" action="#{childActions.editChild(tmp)}"/>
                                     </h:column>
                                     </h:dataTable>
                                     <h:form>
                                     <h:commandButton action="#{childActions.createChild}"/>
                                     </h:form>
                                     </body>
                                    </html>
                                    


                                    Problems are twofold...

                                    1) The parent doesn't seem to be getting set into the session scope. I have no idea how I screwed that one up, since I've never had problems with it before.
                                    2) Even though they're being injected the exact same way, the entity manager in ChildListBean is showing as null.

                                    This is starting to drive me up a wall. :(

                                    I think #2 has to do with the new way I'm dealing with the EntityManager... are you supposed to mix and match the EntityMangager injection and the @PersistenceContext injection?


                                    1 2 Previous Next