Как сделать блокировку базы данных в AppEngine (GAE)? - PullRequest
6 голосов
/ 06 августа 2011

В GAE у меня есть таблица, полная «одноразовых» - такие вещи, как «последний использованный порядковый номер» и тому подобное, которые на самом деле не попадают в другие таблицы.Это простой ключ String-key с парой String-value.

У меня есть некоторый код для захвата именованного целого числа и его увеличения, например, так:

@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;
    }


    // snip...
    @SuppressWarnings("unchecked")
    synchronized
    public static int getIntValueForKeyAndIncrement(String kk, int deFltValue)
    {
        int result = 0;
        OneOff oneOff = null;

        PersistenceManager pm = PMF.get().getPersistenceManager();
        Query query = pm.newQuery(OneOff.class);
        query.setFilter("dataKey == kkParam");
        query.declareParameters("String kkParam");
        List<OneOff> oneOffs = (List<OneOff>) query.execute(kk);

        int count = oneOffs.size();
        if (count == 1)
        {
            oneOff = oneOffs.get(0);
            result = Integer.parseInt(oneOff.value);
        }
        else if (count == 0)
        {
            oneOff = new OneOff(kk, "default");
            result = deFltValue;
        }
        else
        {
                // Log WTF error.
        }

        // update object in DB.
        oneOff.value = "" + (result+1);
        try
        {
            pm.makePersistent(oneOff);
        }
        finally
        {
            pm.close();
        }

        return result;
    }
    // etc...

Однако, когда я делаюэти вызовы:

int val1 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);
int val2 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);
int val3 = OneOff.getIntValueForKeyAndIncrement("someKey", 100);

Иногда я получаю желаемый прирост, а иногда я получаю одно и то же значение.Кажется, что мой доступ к БД работает асинхронно, когда я хотел бы заблокировать БД для этой конкретной транзакции.

Я думал, что

    synchronized
    public static

должен был сделать это для меня,но, очевидно, нет (вероятно, из-за нескольких запущенных экземпляров!)

В любом случае - как мне сделать то, что я хочу?(Я хочу заблокировать свою БД, пока получаю и обновляю это значение, чтобы сделать все это безопасным для параллелизма.)

Спасибо!

== РЕДАКТИРОВАТЬ ==

Я принял правильный ответ Роберта, поскольку транзакции были действительно тем, чего я хотел.Однако для полноты я добавил свой обновленный код ниже.Я думаю, что это правильно, хотя я не уверен насчет предложения if(oneOff==null) (бит try-catch.)

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;
}

1 Ответ

4 голосов
/ 06 августа 2011

Вы должны обновлять свои значения в транзакции . Транзакции App Engine не дадут двум обновлениям перезаписать друг друга, пока ваши чтение и запись находятся в одной транзакции. Обязательно обратите внимание на обсуждение групп сущностей.

...