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 > state, > or the corresponding persistent instance for detached parameter > instances" > :) > > http://download.oracle.com/docs/cd/E13222_01/wls/docs103/kodo/jdo-jav... > > On Jul 12, 7:20 am, mscwd01 <[email protected]> wrote: > > > > > > > > > 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 > > ... > > 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.
