JDO - 使用相同的密钥保留两个实体

时间:2012-12-29 23:37:04

标签: java google-app-engine google-cloud-datastore jdo

我正在开发一个AppEngine项目,我正在AppEngine数据存储区上使用JDO来实现持久性。我有一个实体,它使用编码的字符串作为键,并使用应用程序生成的键名(也是一个字符串)。我这样做是因为我的应用程序经常从野外挖出数据(可能挖出相同的东西)并试图坚持下去。为了避免持久存在几个基本上包含相同数据的实体,我决定散列关于这些数据的一些属性,以获得一致的键名(因为实体关系不直接操纵键)。 现在的问题是,每当我计算我的哈希(keyname)并尝试存储实体时,如果它已经存在于数据存储区中,那么数据存储区(或JDO或罪魁祸首)会在数据存储区中无提示地覆盖实体的属性而不会提出任何例外。这对应用程序有严重影响,因为它会覆盖实体的timeStamps(字段)(我们用于排序)。 我最好能解决这个问题吗?

3 个答案:

答案 0 :(得分:2)

您需要先设置get(检查并设置或CAS)。

CAS是并发的基本租户,它是并行计算的必要之物。

获取比设置便宜得多,所以它实际上可以节省你的钱。

首先检索;而不是盲目写入数据存储区;如果实体不存在,则捕获异常并放置实体。如果确实存在,请在保存之前进行深度比较。如果没有任何改变,请不要坚持(并节省成本)。如果已更改,请随意选择合并策略。保持日期修订的一种(稍微丑陋的)方法是将前一个实体存储为更新实体中的字段(可能不适用于许多修订)。

但是,在这种情况下,你必须先设定。如果你不希望有很多重复,并且想要真正的chintzy,你可以先做一个存在的查询...这是对你想要使用的密钥进行仅密钥计数查询(成本比完全获得少7倍) )。如果(count()== 0)则put()else getAndMaybePut()fi

计数查询语法可能看起来很慢,但从我的基准测试来看,它是判断实体是否存在的最快(也是最便宜)的可能方式:

public boolean exists(Key key){
    Query q;
    if (key.getParent() == null)
      q = new Query(key.getKind());
    else
      q = new Query(key.getKind(), key.getParent());
    q.setKeysOnly();
    q.setFilter(new FilterPredicate(
      Entity.KEY_RESERVED_PROPERTY, FilterOperator.EQUAL, key));
    return 1 == DatastoreServiceFactory.getDatastoreService().prepare(q)
      .countEntities(FetchOptions.Builder.withLimit(1));
}

答案 1 :(得分:1)

在放置()新实体之前,必须执行get()以查看具有相同键的实体是否存在。没有办法做到这一点。

您可以使用memcache和本地“内存中”缓存来加速get()操作。这可能只有在您可能多次读取相同信息时才有用。如果没有,memcache查询实际上可能会减慢您的进程。

为了确保两个请求不会相互覆盖,您应该使用事务(除非您将所有项目放在可能将更新限制为每秒1的单个实体组中,否则不能使用Ajax建议的查询)

在伪代码中:

  1. 从散列数据创建密钥
  2. 检查内存缓存中的密钥(使用ConcurrentHashSet键),如果找到则返回
  3. 检查MemcacheService是否为密钥,如果找到则返回
  4. 开始交易
  5. 从数据存储中获取实体,如果找到则返回
  6. 在数据存储区中创建实体
  7. 提交事务,如果由于并发更新而失败则返回
  8. 将密钥放入缓存(内存和内存缓存)
  9. 如果另一个请求(线程)已经同时写入相同的密钥,则步骤7将失败。

答案 2 :(得分:0)

我建议您不要将ID保存为字符串,而是使用实体的长ID,或者使用由引擎创建的Key数据类型。

   @PersistenceCapable
   public class Test{
     @PrimaryKey
     @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
     private Long ID;

     // getter and setter 
   }
  

每次都会返回一个唯一值。