In GAE, I have a table full of “one pass” - things like “last used sequence number”, etc. that really don't get into other tables. This is a simple String key with a pair of String-value.
I have code to capture a named integer and increment it, for example:
@PersistenceCapable(detachable="true") public class OneOff { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Key key; @Persistent private String dataKey; @Persistent private String value; public OneOff(String kk, String vv) { this.dataKey = kk; this.value = vv; } public static OneOff persistOneOff(String kk, String vv) { OneOff oneoff= new OneOff(kk, vv); PersistenceManager pm = PMF.get().getPersistenceManager(); try { pm.makePersistent(oneoff); } finally { pm.close(); } return oneoff; }
However, when I make these calls:
int val1 = OneOff.getIntValueForKeyAndIncrement("someKey", 100); int val2 = OneOff.getIntValueForKeyAndIncrement("someKey", 100); int val3 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);
Sometimes I get the desired gain, and sometimes I get the same value. It seems that my access to the database works asynchronously when I would like to lock the database for this particular transaction.
I thought that
synchronized public static
should have done this for me, but apparently not (perhaps due to running multiple instances!)
Anyway, how do I do what I want? (I want to lock my DB while I receive and update this value to do all concurrency -safe.)
Thanks!
== EDIT ==
I accepted Robert as the correct answer, since the transactions were, indeed, what I wanted. However, for completeness, I added an updated code below. I think this is correct, although I'm not sure about the if(oneOff==null) (try-catch bit).
public static int getIntValueForKeyAndIncrement(String kk, int defltValue) { int result = 0; Entity oneOff = null; int retries = 3; // Using Datastore Transactions DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); while (true) { com.google.appengine.api.datastore.Transaction txn = datastore.beginTransaction(); try { Key oneOffKey = KeyFactory.createKey("OneOff", kk); oneOff = datastore.get (oneOffKey); result = Integer.parseInt((String) oneOff.getProperty("value")); oneOff.setProperty("value", "" + (result+1)); datastore.put(oneOff); txn.commit(); break; } catch (EntityNotFoundException ex) { result = defltValue; } catch (ConcurrentModificationException ex) { if (--retries < 0) { throw ex; } } if (oneOff == null) { try { Key oneOffKey = KeyFactory.createKey("OneOff", kk); oneOff = new Entity(oneOffKey); oneOff.setProperty("value", "" + (defltValue+1)); datastore.put(txn, oneOff); datastore.put(oneOff); txn.commit(); break; } finally { if (txn.isActive()) { txn.rollback(); } } } else { if (txn.isActive()) { txn.rollback(); } } } return result; }