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.

Reply via email to