One final question. In the afterCompletion method when the userAccount
has the amount subtracted, would you call pm.makePersistent() on the
userAccount object? If you don't it wouldn't persist the change made
to the userAccount surely? If this is the case would you just look to
see that the object returned by makePersistent is not null to ensure
the update to userAccount was saved successfully?

Thanks

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.

Reply via email to