Java JDO-使用相同的密钥持久化两个实体

Java JDO-使用相同的密钥持久化两个实体,java,google-app-engine,google-cloud-datastore,jdo,Java,Google App Engine,Google Cloud Datastore,Jdo,我正在从事一个AppEngine项目,并在AppEngine数据存储上使用JDO实现持久性。我有一个实体,它使用编码字符串作为键,还使用应用程序生成的keyname(也是一个字符串)。我之所以这么做,是因为我的应用程序会频繁地从野外采集数据(可能会采集相同的数据),并试图将它们持久化。为了避免持久化本质上包含相同数据的多个实体,我决定散列这些数据的一些属性,以便获得一致的keyname(由于实体关系,不直接操作键)。 现在的问题是,每当我计算hash(keyname)并试图存储实体时,如果实体已

我正在从事一个AppEngine项目,并在AppEngine数据存储上使用JDO实现持久性。我有一个实体,它使用编码字符串作为键,还使用应用程序生成的keyname(也是一个字符串)。我之所以这么做,是因为我的应用程序会频繁地从野外采集数据(可能会采集相同的数据),并试图将它们持久化。为了避免持久化本质上包含相同数据的多个实体,我决定散列这些数据的一些属性,以便获得一致的keyname(由于实体关系,不直接操作键)。 现在的问题是,每当我计算hash(keyname)并试图存储实体时,如果实体已经存在于数据存储中,那么数据存储(或JDO或罪魁祸首是谁)就会默默地覆盖数据存储中实体的属性,而不会引发任何异常。这会对应用程序产生严重影响,因为它会覆盖实体(我们用于排序)的时间戳(一个字段)。
我怎样才能最好地解决这个问题?

您需要在设置之前进行设置(检查并设置或CAS)

CAS是并发的一个基本租户,也是并行计算的一个必要的缺点

无论如何,GET比SET便宜得多,所以它实际上可能会为您省钱

与其盲目写入数据存储,不如先检索;如果实体不存在,捕获异常并只放置实体。如果确实存在,请在保存之前进行深入比较。如果什么都没有改变,就不要坚持(并节约成本)。如果它发生了变化,请随意选择合并策略。维护带日期修订的一种方法(有点难看)是将以前的实体作为字段存储在更新的实体中(可能不适用于许多修订)

但是,在这种情况下,你必须在设置前获得。如果你不希望有太多的重复项,并且想要真正的中国风格,你可以先做一个exists查询。。。这是对您想要使用的密钥执行仅密钥计数查询(成本比完整获取低7倍)。如果(count()==0),则put()否则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));
}

您需要在设置之前执行get(检查并设置或CAS)

CAS是并发的一个基本租户,也是并行计算的一个必要的缺点

无论如何,GET比SET便宜得多,所以它实际上可能会为您省钱

与其盲目写入数据存储,不如先检索;如果实体不存在,捕获异常并只放置实体。如果确实存在,请在保存之前进行深入比较。如果什么都没有改变,就不要坚持(并节约成本)。如果它发生了变化,请随意选择合并策略。维护带日期修订的一种方法(有点难看)是将以前的实体作为字段存储在更新的实体中(可能不适用于许多修订)

但是,在这种情况下,你必须在设置前获得。如果你不希望有太多的重复项,并且想要真正的中国风格,你可以先做一个exists查询。。。这是对您想要使用的密钥执行仅密钥计数查询(成本比完整获取低7倍)。如果(count()==0),则put()否则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));
}

我建议您不要将ID保存为字符串,而是为实体使用长ID,或者可以使用由appengine自动生成的Key数据类型

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

     // getter and setter 
   }
这将每次向您返回一个唯一的值


我建议您不要将ID保存为字符串,而是为实体使用长ID,或者可以使用由appengine自动生成的Key数据类型

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

     // getter and setter 
   }
这将每次向您返回一个唯一的值

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

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

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

在伪代码中:

  • 从散列数据创建密钥
  • 在内存缓存中检查密钥(使用一组ConcurrentHashKey),如果找到则返回
  • 检查MemcacheService中的密钥,如果找到则返回
  • 启动事务
  • 从数据存储中获取实体,如果找到则返回
  • 在数据存储中创建实体
  • 提交事务,如果由于并发更新而失败,则返回
  • 将密钥放入缓存(内存和memcache中)
  • 如果另一个请求(线程)已同时写入相同的密钥,则步骤7将失败。

    在放入()新实体之前,必须执行get()以查看是否存在具有相同密钥的实体。这样做是没有办法的

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

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

    在伪代码中:

  • 从散列数据创建密钥
  • 在内存缓存中检查密钥(使用一组ConcurrentHashKey),如果找到则返回
  • 检查MemcacheService中的密钥,如果找到则返回
  • 启动事务
  • 从数据存储中获取实体,如果找到则返回
  • 在d中创建实体