2 Replies Latest reply on Feb 27, 2007 5:27 PM by Andrew Wheeler

    JSF and TransactionalSeamPhaseListener

    Andrew Wheeler Newbie

      This is more a question of design or understanding:

      In order to avoid the lazy initialization exception I have followed the seam examples and used the TransactionalSeamPhaseListener and configured the components.xml correctly. The entity manager is now injected using the @In annotation. I can now retrieve my entities without problem however this raises an issue about JSF model updates which over-write the primary key of a ManyToOne entity.

      Example:

      EJB3:
      @Entity
      public class Person extends {
       private Title title;
      
       @ManyToOne(cascade = {}, fetch = FetchType.EAGER)
       @JoinColumn(name = "title_id", unique = false, nullable = true, insertable = true, updatable = true)
       public Title getTitle() {
       return this.title;
       }
       .
       .
      }
      
      Backing Bean:
      @Stateful
      @Scope(ScopeType.CONVERSATION)
      @Name("editPerson")
      public class EditPerson {
       @In
       private EntityManager entityManager;
       @Out
       private Person person;
       .
       .
      }
      
      JSF:
      <h:selectOneMenu id="titleId" value="#{person.title.titleId}"/>
      


      The problem occurs when the title is changed in the selectOneMenu from say "Mr" to "Mrs". JSF applies its model changes to person.title.titleId which changes the primary key of the managed entity. Any calls to persist the person entity fail as two title objects exist with the same primary key This seems to be a general problem for JSF pages wanting to write directly to managed entities.

      To avoid this problem I have reverted to the standard SeamPhaseListener and use the @PersistenceContext. The entity is now "disconnected" and calls to merge() no longer complain about the change in primary key they just perform the update. i.e update person set titleId=2 . . .

      This solution means that I have to initialise every lazy collection manually before passing it to JSF.

      A possible solution is to intercept the model update before it is applied and set the title on person to the changed title:
      newTitleId = {Get model value for titleId}
      newTitle = em.find(Title.class, newTitleId)
      person.setTitle(newTitle)
      {Stop model update of person.title.titleId}
      


      Another solution is have all selectOneMenus writing their value back to a property on the backing bean instead of the entity. This however really curbs flexibility with reuse.

      How should I do this? Is there a better way? Does anyone have a better design solution?