That's awesome, many thanks for taking the time to explain that. On Jul 12, 9:37 am, Nichole <[email protected]> wrote: > Here's an implementation. You might want to add checks for read > staleness, and think about using a task > structure for the operations to make retry easier. > > The unit test structure is from > fromhttp://code.google.com/appengine/docs/java/howto/unittesting.html > > package com.climbwithyourfeet.events.dao; > > import java.util.ArrayList; > import javax.jdo.Transaction; > import javax.jdo.PersistenceManagerFactory; > import javax.jdo.JDOHelper; > 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; > > public class TwoOperationPseudoTransactionTest extends > LocalDatastoreTestCase { > > private Logger log = Logger.getLogger(this.getClass().getName()); > > private UserGameCredits userGameCredits = new UserGameCredits(); > > private UserAccount userAccount = new UserAccount(); > > public TwoOperationPseudoTransactionTest() { > super(); > } > > @Before > public void setUp() throws Exception { > super.setUp(); > } > > @After > public void tearDown() throws Exception { > super.tearDown(); > } > > public class UserGameCredits { > public boolean add(int credits) { > return true; > } > } > > public class UserAccount { > public double getBalance() { > return 123456789.00; > } > public boolean add(int credits) { > return true; > } > public boolean subtractAmount(double balance, double amount, > long timestamp) { > return true; > } > } > > private void handleRetry(UserGameCredits userGameCredits, > UserAccount userAccount) { > } > > @Test > public void testCredits() throws Exception { > > /* > * Goal: > * Update 2 entities which reside in 2 different entity > groups. > * The updates must both pass or both fail. > * > * Example: > * The updates are 2 operations wrapped in a try/catch/ > finally block. > * The entities are UserGameCredits and UserAccount and > are in diff entity groups. > * One transaction, the current transaction, is available > for the application, > * so only one transaction-wrapped-operation can be > rolled back in the > * finally clause if needed. > * > * GameCredits update has higher priority as the user may > need to see > * it immediately. The payment processing may take > longer - so UserAccount consistency > * can have slightly less priority. > */ > > PersistenceManagerFactory pmfInstance = > JDOHelper.getPersistenceManagerFactory("transactions-optional"); > > PersistenceManager pm = null; > Transaction tx = null; > > final List<Boolean> completedOp1 = new ArrayList<Boolean>(); > final List<Boolean> completedOp2 = new ArrayList<Boolean>(); > > int credits = 10; > final double cost = 1; > final double accountBalance = userAccount.getBalance(); > final long timestamp = System.currentTimeMillis(); > > try { > > // change to simulate pass or fail > boolean testShouldPass = false; > > pm = pmfInstance.getPersistenceManager(); > > tx = pm.currentTransaction(); > tx.setIsolationLevel("read-committed"); > tx.begin(); > > tx.setSynchronization(new > javax.transaction.Synchronization() { > public void beforeCompletion() { > // before commit or rollback > log.info("before transaction"); > } > public void afterCompletion(int status) { > if (status == > javax.transaction.Status.STATUS_ROLLEDBACK) { > // rollback > log.severe("rollback transaction: > userGameCredits failed to update. submitting retry"); > handleRetry(userGameCredits, userAccount); > } else if (status == > javax.transaction.Status.STATUS_COMMITTED) { > // commit > log.info("commit: userGameCredits are > updated"); > completedOp1.add(Boolean.TRUE); > // TODO: possibly replace this w/ start in a > task > // The update task should have logic to assert > state before applying operation > boolean done = > userAccount.subtractAmount(accountBalance, cost, timestamp); > completedOp2.add(done); > } > } > > }); > > log.info("updating user game credits"); > //pm.refresh(userAccount); > userGameCredits.add(10); > > pm.flush(); > > if (testShouldPass) { > log.info("committing"); > tx.commit(); > } else { > log.info("rollback"); > tx.rollback(); > } > > } finally { > if ((tx != null) && tx.isActive()){ > tx.rollback(); > log.info("both operations failed. submitting retry"); > handleRetry(userGameCredits, userAccount); > } else { > if (!completedOp1.isEmpty() && > completedOp1.get(0).booleanValue()) { > // TODO: if using task for userAccount update > check completedOp2 and use new task API to see if it is listed, else > start task here. > if (!completedOp2.isEmpty() && > completedOp1.get(0).booleanValue()) { > log.info("both operations succeeded"); > } > } > } > if (pm != null) { > pm.close(); > } > } > > } > > } > > On Jul 11, 3:41 pm, mscwd01 <[email protected]> wrote: > > > > > > > > > The try/catch/finally method seems better than the convoluted method > > mentioned in the blog. One question though, what is the "success flag" > > you mentioned? I'm confused as to how I can have two transactions in > > one try/catch and make sure both succeed. Maybe a quick code sample > > would help if you have the time. > > > Thanks > > > On Jul 11, 5:01 pm, Nichole <[email protected]> wrote: > > > > I might add that you could add a try/catch/finally: use a transaction > > > on the first operation > > > in the try block that can be rolled back if a succeeded flag is > > > false in the finally block; > > > after the 2nd operation in the try block the succeeded gets set to > > > true; and if you > > > use the referred pattern in the blog above on the 2nd operation, > > > you might want to > > > include pre-conditions and the operation to be performed (in other > > > words, if it's a banking operation, you want to > > > know that you expected the amount to be x before you add y). > > > > On Jul 10, 9:15 pm, Didier Durand <[email protected]> wrote: > > > > > Hi, > > > > > Have a look at this to understand the > > > > issues:http://blog.notdot.net/2009/9/Distributed-Transactions-on-App-Engine > > > > > regards > > > > > didier > > > > > On Jul 10, 11:09 pm, mscwd01 <[email protected]> wrote: > > > > > > Hey > > > > > > I'm using JDO and need to update two entities both of which reside in > > > > > their own entity group. As I cannot use a transaction, I'd like to > > > > > determine how others achieve this. It is imperative that both entities > > > > > are updated or none at all. > > > > > > Thanks
-- 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.
