Thanks for the update. It's a pity you cant check to see if the first transaction committed before it leaves an "active" state, that'd make things so much easier. I really needed the two updates to occur as quickly as possible so relying on the second update to continue retying via the task queue, is not ideal. Would running the PersistenceManagerFactory with 'NontransactionalRead' and 'NontransactionalWrite' set to 'true' allow me to do what I want? I cant really find any documentation that describes what those two values do.
Thanks again On Jul 17, 11:46 pm, Nichole <[email protected]> wrote: > Good point, I rewrote the code below to better use the available > connections > and improve the pattern. > > Regarding update 1 being committed and update 2 failing, the > first is already committed, yes. I think one has to use a retry for > the > 2nd update (using the task queue structure) for the 2nd operation to > eventually succeed, but on a longer timescale. > > Here's a better approach to the problem: > > import com.climbwithyourfeet.software.twotransaction.util.PMF; > import com.google.appengine.api.datastore.Key; > import java.util.ArrayList; > import javax.jdo.Transaction; > import javax.jdo.PersistenceManager; > import java.util.logging.Logger; > import com.google.appengine.tools.development.LocalDatastoreTestCase; > import java.util.List; > import org.junit.After; > import org.junit.Before; > import org.junit.Test; > > /** > Goal: > * Update 2 entities which reside in 2 different entity groups. > * The updates must both pass or both fail. > * > * Solution: > * Use a different transaction for each entity, configurable in > jdoconfig.xml. > * (Note that it looks like 2 named PersistenceManagerFactory > connections are possible, but not more than that, and there > is only one transaction for a PMF connection). > * > * Solution is essentially check that transaction1 succeeds or > fails before > * committing transaction2. > * > * The case which needs additional fail-over is the case in which > transaction 1 is committed successfully but transaction2 fails. > * In this case a retry of transaction2 should be invoked and must > eventually succeed. > * > * For that reason, any code using this pattern should design > the logic so that the logic in the 2nd transaction can be > consistent > on a longer timescale. > * > * @author nichole > */ > public class TwoOperationPseudoTransactionTest extends > LocalDatastoreTestCase { > > private final Logger log = > Logger.getLogger(this.getClass().getName()); > private String iden1 = "1234567"; > private String iden2 = "1123456"; > > public TwoOperationPseudoTransactionTest() { > super(); > } > > @Before > public void setUp() throws Exception { > super.setUp(); > > try { > PersistenceManager pm = PMF.get().getPersistenceManager(); > Transaction tx = pm.currentTransaction(); > tx.begin(); > UserGameCredits e1 = new UserGameCredits(iden1); > pm.makePersistent(e1); > pm.flush(); > tx.commit(); > > PersistenceManager pm2 = > PMF.get2().getPersistenceManager(); > tx = pm2.currentTransaction(); > tx.begin(); > UserAccount e2 = new UserAccount(iden2); > pm2.makePersistent(e2); > pm2.flush(); > tx.commit(); > } catch (Throwable t) { > String msg = t.getMessage(); > } > } > > @After > public void tearDown() throws Exception { > super.tearDown(); > } > > @Test > public void test2() throws Exception { > > PersistenceManager pm = PMF.get().getPersistenceManager(); > PersistenceManager pm2 = PMF.get2().getPersistenceManager(); > > final Transaction tx = pm.currentTransaction(); > final Transaction tx2 = pm2.currentTransaction(); > > final List<Boolean> completedOp1 = new ArrayList<Boolean>(); > final List<Boolean> completedOp2 = new ArrayList<Boolean>(); > > try { > > // change to for tests > final boolean commit1 = true; > final boolean commit2 = false; > > tx.setSynchronization(new > javax.transaction.Synchronization() { > public void beforeCompletion() { > log.info("before transaction 1"); > } > public void afterCompletion(int status) { > switch (status) { > case > javax.transaction.Status.STATUS_MARKED_ROLLBACK : > // fall through > case > javax.transaction.Status.STATUS_ROLLEDBACK : > log.severe("rollback transaction 1"); > break; > case > javax.transaction.Status.STATUS_COMMITTED: > log.info("committed transaction 1"); > completedOp1.add(Boolean.TRUE); > break; > case javax.transaction.Status.STATUS_UNKNOWN: > // treat as rollback both? > } > } > }); > tx2.setSynchronization(new > javax.transaction.Synchronization() { > public void beforeCompletion() { > log.info("before transaction 2"); > } > public void afterCompletion(int status) { > > switch (status) { > case > javax.transaction.Status.STATUS_MARKED_ROLLBACK : > // fall through > case > javax.transaction.Status.STATUS_ROLLEDBACK : > log.severe("rollback transaction 2"); > if (!completedOp1.isEmpty() && > completedOp1.get(0)) { > log.severe("1st transaction committed, > but 2nd did not"); > //TODO this is the case we need to > apply application logic for > retry2ndOperation(); > } > break; > case > javax.transaction.Status.STATUS_COMMITTED: > log.info("committed transaction 2"); > completedOp2.add(Boolean.TRUE); > break; > case javax.transaction.Status.STATUS_UNKNOWN: > // treat as rollback both? > } > } > }); > > tx.begin(); > > Key key1 = UserGameCredits.createKey(iden1); > UserGameCredits e1 = > pm.getObjectById(UserGameCredits.class, key1); > e1.setVariable("updated"); > > pm.flush(); > > if (commit1) { > tx.commit(); > } > > if (!completedOp1.isEmpty() && completedOp1.get(0)) { > > tx2.begin(); > > Key key2 = UserAccount.createKey(iden2); > UserAccount e2 = pm.getObjectById(UserAccount.class, > key2); > e2.setVariable("updated"); > > pm2.flush(); > > // test structure that shouldn't be in place for real > code! > // (the retry is in the sync code above) > if (commit2) { > tx2.commit(); > } else { > log.severe("1st transaction committed, but 2nd did > not"); > //TODO this is the case we need to apply > application logic for > retry2ndOperation(); > } > } > > } catch (Throwable t) { > > String msg = t.getMessage(); > > } finally { > > boolean rollback = > ((tx != null) && tx.isActive()) || > ((tx2 != null) && tx2.isActive()); > > if (rollback) { > if (tx.isActive()) > tx.rollback(); > if (tx2.isActive()) > tx2.rollback(); > log.info("both failed"); > } else { > log.info("both succeeded"); > } > > if (pm != null) { > pm.close(); > } > if (pm2 != null) { > pm2.close(); > } > } > > } > > private void retry2ndOperation() { > log.info("This has to succeed eventually using the task queue > structure"); > } > > } > > On Jul 17, 7:23 am, mscwd01 <[email protected]> wrote: > > > > > > > > > Okay, so after testing this, I have one issue. > > If the second update fails (in the afterCompletion method) there is no > > way to rollback the transaction to prevent the first update (adding 10 > > to userGameCredits object) taking effect - as the transaction is no > > longer active at this point. > > Am I missing something here or is there no way to rollback the > > transaction once inside the afterCompletion method? > > > I am returned this exception if I try to rollback the transaction. > > Would doing what it says be acceptable or is setting > > NontransactionalRead and NontransactionalWrite to true a "bad" thing > > to do? > > org.datanucleus.jdo.exceptions.TransactionNotActiveException: > > Transaction is not active. You either need to define a transaction > > around this, or run your PersistenceManagerFactory with > > 'NontransactionalRead' and 'NontransactionalWrite' set to 'true' > > > Thanks! > > > On Jul 13, 2:53 am,Nichole<[email protected]> wrote: > > > > Yes, you'll need to add structure to the entity update method or > > > surrounding it to persist the state of the entity (and use real > > > entities! > > > The test structure is purely to provide runnable code to demonstrate > > > one way to approach the problem). > > > > For the 2nd update, if you recently fetched or refreshed the entity > > > and the persistenceManager is still open, then the entity is still > > > attached and all you need to use is persistenceManager.flush() before > > > you close it. It doesn't do harm, however, to use > > > makePersistent(entity) > > > if the entity isn't a detached transient instance and you certainly > > > do > > > want to use makePersistent(entity) if it is. > > > > And yes, the return value of makePersistent(entity) is a good check > > > for the last operation being successful: the return value is > > > "the parameter instance for parameters in the transient or persistent > > ... > > read more » -- You received this message because you are subscribed to the Google Groups "Google App Engine for Java" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/google-appengine-java?hl=en.
