-
1. Re: using sequences to prevent premature flush of SMPC
gonorrhea Apr 24, 2009 7:10 PM (in response to 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 Apr 24, 2009 7:15 PM (in response to 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 Apr 24, 2009 8:11 PM (in response to 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 Apr 25, 2009 7:48 AM (in response to 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?