4 Replies Latest reply on Apr 25, 2009 7:48 AM by gonorrhea

    using sequences to prevent premature flush of SMPC

    gonorrhea

      consider this excerpt from Seam in Action:




          Hibernate’s MANUAL flush mode ensures that the persistence context is only flushed when a call is made to the flush() method on the persistence manager API (beanmanaged flushing). This mode gives you the flexibility to take your persistence context in and out of transactions as you please without risking a premature flush.



          WARNING: If the entity identifier is generated during an insertion (i.e., auto-increment column), then even with manual flushing, a flush occurs after a call to persist(). This is necessary since each managed entity in the persistence context must be assigned an identifier. To avoid the flush, you need to set the id-generation strategy to sequence (not identity).

      Anybody have any tips on how to handle this situation if you have entities demarcated as such for the PK getter method:


             

      @Id
           @GeneratedValue(strategy=GenerationType.IDENTITY)
           @Column(name = "ListValueID", unique = true, nullable = false)
           @NotNull
           public int getListValueEntityId() {
                return this.listValueEntityId;
           }



      We are using IDENTITY columns in MSSQL 2005, is it possible to use sequences or is there a workaround to this problem?  thx.



        • 1. Re: using sequences to prevent premature flush of SMPC
          gonorrhea

          I just did a POC app to test out DAllen's hypothesis.  He is definitely correct.  For application transactions using MANUAL flushModeType and SMPC involving inserts, this is a problem b/c atomicity for the application tx is lost (i.e. there are multiple tx's begin/commit for persist() operations). 


          In the following mini-app, the flush for the update (merge) operations occurs in the @End method, as designed/intended (not prematurely).  The flush for the insert (persist) operations occur prematurely after this line is executed:


          entityManager.persist(securityRole);


          What is the solution/workaround for this?  If it's in the Bauer/King book, plz point out.  Perhaps this is one reason why MANUAL flush is not part of the JPA spec.  thx.


          .xhtml:


          <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                                "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
          
          <ui:composition xmlns="http://www.w3.org/1999/xhtml"
               xmlns:ui="http://java.sun.com/jsf/facelets"
               xmlns:h="http://java.sun.com/jsf/html"
               xmlns:f="http://java.sun.com/jsf/core"
               xmlns:a4j="http://richfaces.org/a4j"
               xmlns:rich="http://richfaces.org/rich"
               xmlns:s="http://jboss.com/products/seam/taglib"
               template="/templates/normal.xhtml">
               
               <ui:define name="body">
                    
                    <h:form>     
                         <h:commandButton value="insertA - start conv" action="#{testIdentityInsert.persistA}"/>          
                         <h:commandButton value="insertB" action="#{testIdentityInsert.persistB}"/>          
                         <h:commandButton value="insertC - end conv" action="#{testIdentityInsert.persistC}"/>     
                    </h:form>
                    
                    <h:form>
                         <h:commandButton value="updateA - start conv" action="#{testIdentityUpdate.updateA}"/>          
                         <h:commandButton value="updateB" action="#{testIdentityUpdate.updateB}"/>          
                         <h:commandButton value="updateC - end conv" action="#{testIdentityUpdate.updateC}"/>
                    </h:form>          
                    
               </ui:define>
          
          </ui:composition>



          TestIdentityInsertAction:


          @Name("testIdentityInsert")
          @Stateful
          public class TestIdentityInsertAction implements TestIdentityInsertLocal {
          
               @In 
               private EntityManager entityManager;
               
               @Logger
               private Log log;
               
               ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
               
               @Remove @Destroy
               public void destroy() {}
               
               @Begin(join=true, flushMode=FlushModeType.MANUAL)
               public void persistA() {
                    SecurityRole securityRole = new SecurityRole("arbitest1", "FAKE_AD_GROUP_1", "desc1", new Date());
                    entityManager.persist(securityRole);
               }
          
               public void persistB() {
                    SecurityRole securityRole = new SecurityRole("arbitest2", "FAKE_AD_GROUP_2", "desc2", new Date());
                    entityManager.persist(securityRole);
               }
          
               @End
               public void persistC() {
                    SecurityRole securityRole = new SecurityRole("arbitest3", "FAKE_AD_GROUP_3", "desc3", new Date());
                    entityManager.persist(securityRole);
                    entityManager.flush();
               }
          
          }



          TestIdentityUpdateAction:


          @Name("testIdentityUpdate")
          @Stateful
          public class TestIdentityUpdateAction implements TestIdentityUpdateLocal {
          
               @In 
               private EntityManager entityManager;
               
               @Logger
               private Log log;
               
               ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
               
               @Remove @Destroy
               public void destroy() {}
               
               @Begin(join=true, flushMode=FlushModeType.MANUAL)
               public void updateA() {
                    SecurityRole securityRole = entityManager.find(SecurityRole.class, Short.parseShort("4"));
                    securityRole.setSecurityRoleAdGroupName("the new name1");
                    securityRole.setAddedDate(new Date());
                    entityManager.merge(securityRole);
               }
          
               public void updateB() {
                    SecurityRole securityRole = entityManager.find(SecurityRole.class, Short.parseShort("2"));
                    securityRole.setSecurityRoleAdGroupName("the new name2");
                    securityRole.setAddedDate(new Date());
                    entityManager.merge(securityRole);
               }
          
               @End
               public void updateC() {
                    SecurityRole securityRole = entityManager.find(SecurityRole.class, Short.parseShort("3"));
                    securityRole.setSecurityRoleAdGroupName("the new name3");
                    securityRole.setAddedDate(new Date());
                    entityManager.merge(securityRole);
                    entityManager.flush();
               }
          
          }

          • 2. Re: using sequences to prevent premature flush of SMPC
            gonorrhea

            I changed to sequence as suggested by DAllen (but MSSQL doesn't support sequences, so that may be a problem :)


            @Id
                 //@GeneratedValue(strategy=GenerationType.IDENTITY)
                 @GeneratedValue(strategy=GenerationType.SEQUENCE)
                 @Column(name = "SECURITY_ROLE_ID", unique = true, nullable = false)
                 public short getSecurityRoleId() {
                      return this.securityRoleId;
                 }



            and got this stack trace on deployment:


            Caused by: org.hibernate.MappingException: could not instantiate id generator
                 at org.hibernate.id.IdentifierGeneratorFactory.create(IdentifierGeneratorFactory.java:98)
                 at org.hibernate.mapping.SimpleValue.createIdentifierGenerator(SimpleValue.java:152)
                 at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:192)
                 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1300)
                 at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:859)
                 at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:669)
                 ... 99 more
            Caused by: org.hibernate.MappingException: Dialect does not support sequences
                 at org.hibernate.dialect.Dialect.getSequenceNextValString(Dialect.java:596)
                 at org.hibernate.id.SequenceGenerator.configure(SequenceGenerator.java:65)
                 at org.hibernate.id.SequenceHiLoGenerator.configure(SequenceHiLoGenerator.java:43)
                 at org.hibernate.id.IdentifierGeneratorFactory.create(IdentifierGeneratorFactory.java:94)
                 ... 104 more

            • 3. Re: using sequences to prevent premature flush of SMPC
              gonorrhea

              so here's a potential workaround (that works but not sure if it's the best alternative).  Strategy is to use context variables and to exec persist() calls only in the @End method and then flush() SMPC...


              @Name("testIdentityInsert")
              @Stateful
              public class TestIdentityInsertAction implements TestIdentityInsertLocal {
              
                   @In 
                   private EntityManager entityManager;
                   
                   @Logger
                   private Log log;
                   
                   private SecurityRole securityRoleA;
                   private SecurityRole securityRoleB;
                   private SecurityRole securityRoleC;
                   
                   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                   
                   @Remove @Destroy
                   public void destroy() {}
                   
                        
                   @Begin(join=true, flushMode=FlushModeType.MANUAL)
                   public void persistA() {
                        securityRoleA = new SecurityRole("arbitest1", "FAKE_AD_GROUP_1", "desc1", new Date());
                        
                   }
              
                   public void persistB() {
                        securityRoleB = new SecurityRole("arbitest2", "FAKE_AD_GROUP_2", "desc2", new Date());
                        
                   }
              
                   @End
                   public void persistC() {
                        securityRoleC = new SecurityRole("arbitest3", "FAKE_AD_GROUP_3", "desc3", new Date());
                        entityManager.persist(securityRoleA);
                        entityManager.persist(securityRoleB);
                        entityManager.persist(securityRoleC);
                        entityManager.flush();
                   }
              
              }
              

              • 4. Re: using sequences to prevent premature flush of SMPC
                gonorrhea

                There is very good coverage of this problem in JPA/Hibernate book:




                One solution uses compensation actions that you execute to undo any possible insertions made during a conversation that is aborted, in addition to closing the unflushed persistence context.  You'd have to manually delete the row that was inserted.  Another solution is a different identifier generator, such as a sequence, that supports generation of new identifier values without insertion.

                In my case with MSSQL, the latter solution is not possible.


                However, the example that is discussed in that section uses the Hibernate Session API save() method call.  Bauer goes on to say that with persist(), it can delay insertions, even with post-insert identifier generation, if you call it outside of a transaction.  The persist() method can delay inserts b/c it doesn't have to return an identifier value.


                So the question is: when using JPA, is it good/bad practice to not use transactions (i.e., NOT_SUPPORTED transaction attribute type CMT tx demarcation) to prevent the premature insert from happening?