11 Replies Latest reply on Aug 14, 2008 10:40 AM by Jens Weintraut

    EntityManager: INSERT and UPDATE instead of single INSERT

    Jens Weintraut Apprentice

      Hi folks,


      I know that this issue doesn't really fit into a Seam forum, but here are a lot of experts so I hope to get help.


      So let me explain my problem: I have a page where the user can create a new item. After filling out all necessary fields he can choose to just save the item, i. e. permit it and create a new one. Or he can save and edit it, i. e. permit it and redisplay it so that the user can make some changes and update this item.


      In order to realize this functionality I created to functions in my bean:


       


      public String createTestaction() {
          boolean success = persistCreatedTestaction();
          
          if(success) {
            lastSavedTestactionID = testactionToCreate.getID();
            initTestactionToCreate();
            return "valid";
          } else {
            lastSavedTestactionID = null;
            return "invalid";
          }
          
        }
        
        public String createTestactionAndEdit() {
          boolean success = persistCreatedTestaction();
          
          if(success) {
            lastSavedTestactionID = testactionToCreate.getID();
            return "valid";
          } else {
            lastSavedTestactionID = null;
            return "invalid";
          }
          
        }
      



      As you can see those methods do exactly the same except this call to initTestactionToCreate() in createTestaction(). This is just to intitialze a new item:


      @Factory("testactionToCreate")
      public void initTestactionToCreate() {
        testactionToCreate = testactionService.getTestactionForCreation();
      }



      All this testactionService method does is to return a new Testaction() with some attributes set. There's no call to the EntityManager in initTestactionToCreate().


      OK. When I test my application, fill in those fields and hit the createTestaction button, the app does what it should do. In the server log I can see, that there is an INSERT done and the new testaction is saved.
      But when I hit createTestactionAndEdit the EntityManager creates an INSERT and an UPDATE statement and I don't know why. This is especially problematic since on UPDATE a database trigger is fired which fails at this stage.
      And this misbehaviour really results from the missing call to initTestactionToCreate(); I added a call and it worked.


      So why does the absence of this call result in an additional UPDATE statement? Does anybody know how I can solve this or try to get a clue on this?


      Thanks in advance
      Newlukai

        • 1. Re: EntityManager: INSERT and UPDATE instead of single INSERT
          Guillaume Jeudy Master

          You don't give enough info on your setup. Can you give facelet sample, corresponding backing bean definitions with annotations and the entitymanager management strategy either SMPC or CMPC, flushMode strategy, etc...


          The only possible cause I can think of is your

          testactionToCreate

          gets dirty after the call to
          entityManager.persist()

          . Hibernate will automatically detect dirtiness of it's managed entities before flushing the entityManager which would result in an UPDATE statement being fired at the database.

          • 2. Re: EntityManager: INSERT and UPDATE instead of single INSERT
            Jens Weintraut Apprentice

            Thanks. Here are the information you asked for:


            This is the interesting part of the backing bean of that page:



            @Stateful
            @Scope(ScopeType.SESSION)
            @Name("createTestaction")
            public class CreateTestaction implements ICreateTestaction, Serializable {
              private static final long serialVersionUID = 7626485366852012295L;
            
              @In
              private transient FacesContext facesContext;
              
              @In
              private transient ITestactionService testactionService;
              
              @In(required=false)
              @Valid
              private User user;
              
              @Valid
              @In(required=false)
              @Out(required=false)
              private Testaction testactionToCreate;
              
              @Factory("testactionToCreate")
              public void initTestactionToCreate() {
                testactionToCreate = testactionService.getTestactionForCreation();
              }
              
              public String newTestaction() {
                initTestactionToCreate();
                lastSavedTestactionID = null;
                return "";
              }
              
              public String createTestaction() {
                boolean success = persistCreatedTestaction();
                
                if(success) {
                  lastSavedTestactionID = testactionToCreate.getID();
                  initTestactionToCreate();
                  return "valid";
                } else {
                  lastSavedTestactionID = null;
                  return "invalid";
                }
                
              }
              
              public String createTestactionAndEdit() {
                boolean success = persistCreatedTestaction();
                
                if(success) {
                  lastSavedTestactionID = testactionToCreate.getID();
            //      initTestactionToCreate();
                  return "valid";
                } else {
                  lastSavedTestactionID = null;
                  return "invalid";
                }
              }



            and this is how the page looks like:



            <ice:form id="createTestaction">
              <ui:include src="inc/messages.xhtml" />
            
              <s:div styleClass="buttonLine">
                <ice:commandButton partialSubmit="true" actionListener="#{createTestaction.showUpDownloadDialog}" rendered="#{createTestaction.lastSavedTestactionID == testactionToCreate.ID}" />
                <h:outputText value="(#{createTestaction.countOfAttachedFiles})" converter="javax.faces.Long" rendered="#{(createTestaction.lastSavedTestactionID == testactionToCreate.ID) and (createTestaction.countOfAttachedFiles > 0)}" />
            
                <ice:commandButton partialSubmit="true" action="#{createTestaction.newTestaction}" title="#{ares_messages.tooltip_newTestaction}" rendered="#{createTestaction.lastSavedTestactionID == testactionToCreate.ID}" />
                <h:commandButton action="#{createTestaction.createTestactionAndEdit}" title="#{ares_messages.tooltip_createTestactionAndRedisplay}" />
                <h:commandButton action="#{createTestaction.createTestaction}" title="#{ares_messages.tooltip_createTestaction}" />
              </s:div>
            
              <s:div styleClass="furtherInfo">
                <h:panelGrid columns="2">
                  <h:outputText value="#{ares_messages.filter_release}" />
                  <h:outputText value="#{ares_messages.filter_releasestatus}" />
            
                  <ice:selectOneMenu value="#{releaseSelector.selectedRelease}" valueChangeListener="#{createTestaction.changedRelease}" partialSubmit="true" id="releaseSelector" converter="com.idsscheer.ares.view.converter.ReleaseConverter">
                    <s:selectItems value="#{releaseSelector.releases}" var="release" label="#{release.relToken} (#{release.ID})" />
                  </ice:selectOneMenu>
             
                  <ice:selectOneMenu value="#{releasestatusSelector.selectedReleasestatus}" valueChangeListener="#{createTestaction.changedRelease}" partialSubmit="true" id="releasestatusSelector" converter="com.idsscheer.ares.view.converter.ReleasestatusConverter">
                    <s:selectItems value="#{releasestatusSelector.releasestatus}" var="releasestatus" label="#{releasestatus.token} (#{releasestatus.ID})" />
                  </ice:selectOneMenu>
                </h:panelGrid>
              </s:div>
             
              <s:div style="display: #{(createTestaction.lastSavedTestactionID == null) ? 'none' : 'block'};">
                <h:outputText value="#{ares_messages.label_createTestaction_lastSavedTestactionID} #{createTestaction.lastSavedTestactionID}" />
              </s:div>
            
            
              <h:panelGrid cellpadding="0" cellspacing="0" border="0" columns="5">
                <h:outputText value="#{ares_messages.label_testaction_TesterUsrID}" />
                <h:outputText value="#{ares_messages.label_testaction_DevUsrID}" />
            
                <ice:selectOneMenu value="#{testactionToCreate.testerUsrID}" id="testerUsrID" converter="com.idsscheer.ares.view.converter.UserConverter">
                  <s:selectItems value="#{mainSupplier.testers}" var="tester" label="#{tester.ID} - #{tester.name}" noSelectionLabel="#{ares_messages.filter_not_specified}" />
                </ice:selectOneMenu>
            
                <ice:selectOneMenu value="#{testactionToCreate.devUsrID}" id="devUsrID" converter="com.idsscheer.ares.view.converter.UserConverter">
                  <s:selectItems value="#{mainSupplier.developers}" var="developer" label="#{developer.ID} - #{developer.name}" />
                </ice:selectOneMenu>
            

            .


            The configuration. SMPC or CMPC? Good question. I hop this excerpt from my components.xml helps:


            <transaction:ejb-transaction />
            
            <persistence:managed-persistence-context
              name="em"
              auto-create="true"
              persistence-unit-jndi-name="java:/entityManagerFactories/data"/>




            I wonder why this trigger fails. It tries to calculate something from one attribute of this testactionToCreate. If this attribute is missing, a SELECT statement in a function used by this trigger fails, since this attribute is used in the WHERE clause.
            If I save this testaction, everything works fine and this attribute is persisted. But if I hit save and edit, the trigger fails what tells me that in either the INSERT or the UPDATE statement this attribute is missing. But wif I debug, this attribute is given.


            Thanks in advance
            Newlukai

            • 3. Re: EntityManager: INSERT and UPDATE instead of single INSERT
              Guillaume Jeudy Master

              You are most probably using SMPC. Can you provide the DAO code?

              • 4. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                Jens Weintraut Apprentice

                Since I'm not an expert in these things, just a stupid student trying to understand and work with these technologies, I'm not sure what you mean with DAO code. But I think you mean that code that is responsible for persisting.


                So here's the createTestaction component which executes the chosen action:


                @Stateful
                @Scope(ScopeType.SESSION)
                @Name("createTestaction")
                public class CreateTestaction implements ICreateTestaction, Serializable {  
                  @In
                  private transient ITestactionService testactionService;
                  
                  @In(required=false)
                  @Valid
                  private User user;
                  
                  @Valid
                  @In(required=false)
                  @Out(required=false)
                  private Testaction testactionToCreate;
                  
                  @In(required=false)
                  private ITestcaseSelector testcaseSelector;
                  
                  @Factory("testactionToCreate")
                  public void initTestactionToCreate() {
                    testactionToCreate = testactionService.getTestactionForCreation(testcaseSelector.getSelectedTestcase(), user);
                  }
                  
                  public String createTestaction() {
                    boolean success = persistCreatedTestaction();
                    
                    if(success) {
                      lastSavedTestactionID = testactionToCreate.getID();
                      initTestactionToCreate();
                      return "valid";
                    } else {
                      lastSavedTestactionID = null;
                      return "invalid";
                    }
                    
                  }
                  
                  public String createTestactionAndEdit() {
                    boolean success = persistCreatedTestaction();
                    
                    if(success) {
                      lastSavedTestactionID = testactionToCreate.getID();
                //      initTestactionToCreate();
                      return "valid";
                    } else {
                      lastSavedTestactionID = null;
                      return "invalid";
                    }
                    
                  }
                  
                  private boolean persistCreatedTestaction() {
                    try {
                      testactionService.validateAndPrepareTestactionToCreate(testactionToCreate, user, releaseSelector.getSelectedRelease());
                    } catch (MissingPropertiesException e) {
                      if(e.hasMissingProperties()) {
                        MessageMap map = MissingPropertyMessageMap.getInstance();
                        
                        for(AresProperty property : e.getMissingProperties()) {
                          FacesUtil.addMessage(map.getID(property), map.getGlobalMessage(property), map.getDetailMessage(property));
                        }
                        
                        return false;
                      }
                    }
                    
                    testactionService.persistAndProtocol(testactionToCreate, null, user);
                    Events.instance().raiseEvent("com.idsscheer.ares.testaction.saved");
                    
                    return true;
                  }
                }
                



                In my application this backing bean is a component of the view part and delegates the database access to the business part, the testactionService:



                @Stateless
                @Name("testactionService")
                @AutoCreate
                public class TestactionService implements ITestactionService {
                  @PersistenceContext(unitName = "aresDatabase")
                  private transient EntityManager em;
                  
                  @In
                  private IProtocolService protocolService;
                  
                  @In
                  private ITestobjectService testobjectService;
                  
                  
                  public Testaction getTestactionForCreation(Testcase selectedTestcase, User user) {
                    Testaction testactionForCreation = new Testaction();
                    testactionForCreation.setTesterUsrID(user);
                    testactionForCreation.setValidatorUsrID(user);
                
                    testactionForCreation.setTObjID((selectedTestcase == null) ? null : selectedTestcase.getTObjID());
                    testactionForCreation.setQATID((selectedTestcase == null) ? null : selectedTestcase.getQATID());
                    testactionForCreation.setCompID((selectedTestcase == null) ? null : testobjectService.getComponent(selectedTestcase.getCompID()));
                    
                    if(selectedTestcase != null) {
                      testactionForCreation.setTCaseToken(selectedTestcase.getToken());
                      testactionForCreation.setTCaseDescr(selectedTestcase.getDescr());
                    }
                    
                    return testactionForCreation;
                  }
                  
                  public void validateAndPrepareTestactionToCreate(Testaction testactionToCreate, User user, Release selectedRelease) throws MissingPropertiesException {
                    MissingPropertiesException exception = new MissingPropertiesException();
                    
                    if(testactionToCreate.getTCaseToken() == null || testactionToCreate.getTCaseToken().trim().length() == 0) {
                      exception.addProperty(AresProperty.TA_TESTCASE_TOKEN);
                    }
                    if(testactionToCreate.getTCaseDescr() == null || testactionToCreate.getTCaseDescr().trim().length() == 0) {
                      exception.addProperty(AresProperty.TA_TESTCASE_DESCRIPTION);
                    }
                    if(testactionToCreate.getTCaseExpResult() == null || testactionToCreate.getTCaseExpResult().trim().length() == 0) {
                      exception.addProperty(AresProperty.TA_TESTCASE_EXPECTEDRESULT);
                    }
                    if(testactionToCreate.getDefectDescr() == null || testactionToCreate.getDefectDescr().trim().length() == 0) {
                      exception.addProperty(AresProperty.TA_DEFECT_DESCRITPION);
                    }
                    
                    if(exception.hasMissingProperties()) {
                      throw exception;
                    }
                    
                    if(testactionToCreate.getTesterUsrID() == null) {
                      testactionToCreate.setTesterUsrID(user);
                    }
                    
                    testactionToCreate.setTDate(new Date());
                    testactionToCreate.setRelID(selectedRelease);
                    testactionToCreate.setRevID(em.find(Revisionclass.class, Revisionclass.Constants.TO_BUGFIX.value()));
                    testactionToCreate.setBFVID(em.find(BugfixValidation.class, BugfixValidation.Constants.NOT_A_BUG.value()));
                    testactionToCreate.setDevRelID(selectedRelease);
                  }
                  
                  public void persistAndProtocol(Testaction testaction, Testaction oldTestaction, User user) {
                    if(oldTestaction == null) {
                      oldTestaction = em.find(Testaction.class, testaction.getID());
                    }
                    
                    if(oldTestaction == null) {
                      em.persist(testaction);
                      protocolService.protocolChanges(user, new Testaction(), testaction);
                    } else if(testaction.getID() == oldTestaction.getID()) {
                      em.merge(testaction);
                      protocolService.protocolChanges(user, oldTestaction, testaction);
                    }
                  }



                I hope I didn't forget anything. And I hope you don't mind that I post only relevant code. I do this because I want to keep my explanations as small as possible and avoid you spending time on reading irrelevant code.

                • 5. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                  Guillaume Jeudy Master

                  I think you use seam managed persistence context, the default scope of it being conversation. Since you have no explicit conversation going on it lasts for the duration of the request cycle.


                  Seam uses 2 transactions per request cycle, one enclosing INVOKE APPLICATION phase, the other RENDER RESPONSE. In case of createTestactionAndEdit action that action gets persisted in database at the end of T1 and remains managed during the T2 in the RENDER RESPONSE phase. It remains managed (in the persistence context) because the call to
                  initTestactionToCreate
                  is not issued, not overwriting the testactionToCreate reference to be outjected in the session scope. RENDER RESPONSE will resolve that instance for every reference to the matching

                  #{testactionToCreate}

                  in your view potentially altering it.


                  My guess is that a setter is called on the testaction bean during that phase causing hibernate to launch the UPDATE on its dirty check at the end of T2.


                  You can put breakpoints on your Testaction setters to see what is going on at this point. You can also look at hibernate interceptor framework and log informations on the onFlushDirty event. This will show you previous versus actual state and exactly which fields are different.

                  • 6. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                    Jens Weintraut Apprentice

                    Wow. Thanks. This gives me an idea of how this whole stuff is working. I'll see what's going on the bean as soon as I'm at work again.

                    • 7. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                      Jens Weintraut Apprentice

                      Thanks for your hint where to debug. Now I know that my own code changes the testactionToCreate and causes this crash.


                      I can't say whether it's T1 or T2, but during one of them this method is called:


                      public void changedRelease(ValueChangeEvent event) {
                        if(FacesUtil.postponeInvocation(event)) {
                          return;
                        }
                      
                        Release release = null;
                      
                        if(event.getNewValue() != null && event.getNewValue() instanceof Release) {
                          release = (Release) event.getNewValue();
                        }
                      
                        List<Testcase> testcases = testcaseService.getTestcases(release);
                        Testcase newTestcase = null;
                        if(testcases.size() > 0) {
                          newTestcase = testcases.get(0);
                        }
                      
                        modifyTestaction(testcaseSelector.getSelectedTestcase(), newTestcase);
                      }



                      First, the problematic setter on testactionToCreate is called with a valid value. After that, the valueChangeListener is invoked which then calls the problematic setter with an invalid value.


                      This happens on both actions saveTestaction and saveTestactionAndEdit. So I don't know, when this valueChangeListener is called. Mybe at the end of T1 while RENDER_RESPONSE or it's an additional request? I don't know.
                      But this valueChangeListener should not be called, since there's no change made by the user to one of those selectOneMenus.


                      Additionally I have to check my valueChangeListener since it can transform this testactionToCreate in an invalid state, which should not happen, too.

                      • 8. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                        Guillaume Jeudy Master

                        I thought your INSERT and UPDATE scenario occurred when hitting the commandbutton during a single request..


                        <h:commandButton action="#{createTestaction.createTestactionAndEdit}" title="#{ares_messages.tooltip_createTestactionAndRedisplay}" />



                        The valueChangeListener required additionnal user manipulations (i.e. change values in your select menus...). Can you please clarify your usecase?

                        • 9. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                          Jens Weintraut Apprentice

                          I'm sorry. Of course I'll try to make it as clear as possible.


                          You are right. This INSERT and UPDATE scenario occurs in the request which starts after hitting the button you mentioned, i. e. The user hits this button and Hibernate executes an INSERT and an UPDATE, then the page is rerendered (with an error message).


                          Following your hint where to debug I saw that during this request (the user only hits the button, nothing else is done) a valueChangeListener of a selectOneMenu is invoked.


                          It's the second selectOneMenu from this part of the page:


                            <s:div styleClass="furtherInfo">
                              <h:panelGrid columns="2">
                                <h:outputText value="#{ares_messages.filter_release}" />
                                <h:outputText value="#{ares_messages.filter_releasestatus}" />
                          
                                <ice:selectOneMenu value="#{releaseSelector.selectedRelease}" valueChangeListener="#{createTestaction.changedRelease}" partialSubmit="true" id="releaseSelector" converter="com.idsscheer.ares.view.converter.ReleaseConverter">
                                  <s:selectItems value="#{releaseSelector.releases}" var="release" label="#{release.relToken} (#{release.ID})" />
                                </ice:selectOneMenu>
                           
                                <ice:selectOneMenu value="#{releasestatusSelector.selectedReleasestatus}" valueChangeListener="#{createTestaction.changedRelease}" partialSubmit="true" id="releasestatusSelector" converter="com.idsscheer.ares.view.converter.ReleasestatusConverter">
                                  <s:selectItems value="#{releasestatusSelector.releasestatus}" var="releasestatus" label="#{releasestatus.token} (#{releasestatus.ID})" />
                                </ice:selectOneMenu>
                              </h:panelGrid>
                            </s:div>



                          So, once again: The user hits the button saveTestactionAndEdit and a request is started. During this request the valueChangeListener is invoked, without the user having changed any of these selectOneMenus.
                          This valueChangeListener changes the testactionToCreate which results in an additional UPDATE. And since this change drives the testaction to an invalid state, this UPDATE crashes when hitting saveTestactionAndEdit.


                          This valueChangeListener is also called when the user hits saveTestaction and it changes the testaction the same way. But it seems that there is no UPDATE. And I wonder why.


                          I changed the valueChangeListener to keep the testaction in a valid state, so there is no crash anymore cause by an UPDATE with an invalid testaction. But I really would like to know, why this valueChangeListener is called during this request the user started with hitting this button.


                          Oooh. This information might be interesting: I'm using ICEfaces 1.7.1. The selectOneMenus have the partialSubmit flag set, so the valueChangeListener is immediately called after changing a selectOneMenu. Perhaps there is a problem between Seam and ICEfaces which causes the valueChangeListener to be called after hitting the button?

                          • 10. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                            Guillaume Jeudy Master

                            ah now thats clearer. I guess the reason why there is no update on saveTestaction is because you overwrite the member variable:


                            @In(required=false)
                              @Out(required=false)
                              private Testaction testactionToCreate;
                            



                            with a new reference outjecting it at the end of the INVOKE APPLICATION phase short-circuiting any dirty setter action that may happen later (the new reference is transient so won't be managed by hibernate.


                            Your code gets executed in INVOKE APPLICATION JSF phase so it would mean that the valueChangeListener gets invoked in the RENDER RESPONSE phase ? It doesn't seem to make sense to me, normally a valueChangeListener is invoked in earlier phases (APPLY REQUEST VALUE or PROCESS VALIDATIONS).


                            I suggest you check ICE faces documentation and post on their forum as well to understand exactly how their component is working with partialSubmit...


                            One other thing you can do to help debug is implement your own JSF phase listener to understand when the code is executed in the JSF lifecycle.


                            Creating JSF phase listener


                            You will need to register your custom phase listener in the faces-config.xml


                            <lifecycle>
                               <phase-listener>a.b.c.MyPhaseListener</phase-listener>
                            </lifecycle>   

                            • 11. Re: EntityManager: INSERT and UPDATE instead of single INSERT
                              Jens Weintraut Apprentice

                              Ah, OK. Now it's clear why saveTestaction results in just one INSERT.


                              Regarding the problem why the valueChangeListener is fired, I tried to implement such a phase listener. The problem was that it never was invoked. I don't know why, but I got another hint from the ICEfaces forum.
                              I had a mistake in my design. This backing bean which provides one of those lists for selectOneMenu didn't preset its selected value. And the user doesn't have the possibility to choose a null value. Thus the value changed from null to the first available value in the list in order to render the page correctly. And this change, which wasn't done by the user, lead to the invocation of the valueChangeListener. So I just added a @Create method which presets the selected value.


                              Thank you very much for your help.