【问题标题】:Can a two Entities of the same Kind have the same ID if the parent is different?如果父级不同,两个相同种类的实体可以具有相同的 ID 吗?
【发布时间】:2016-04-26 20:44:45
【问题描述】:

我知道数据存储区会自动为根实体生成一个唯一 ID。但是,具有不同父母的同一种类的实体呢?

数据存储是否会自动为具有不同父级(相同种类)的相同种类的实体生成唯一 ID?例如User->Post。可以想象两个不同的用户都有一个具有相同 ID 的帖子吗?

【问题讨论】:

  • 嗯,答案显然是肯定的。我想你想问即使父母不同,allocateId 是否会给你唯一的 id。

标签: java google-app-engine google-cloud-endpoints google-cloud-datastore objectify


【解决方案1】:

我为你写了一个 JUnit 测试。它使用 lombok,但你也可以写出 getter 和 setter。

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.googlecode.objectify.*;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.util.Closeable;
import junit.framework.Assert;
import lombok.Getter;
import lombok.Setter;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.List;

public class IdAllocationTest {
    @Entity
    public static class ChildEntity {
        @Parent
        @Getter
        @Setter
        private Ref<ParentEntity> parent;
        @Id
        @Getter
        @Setter
        private Long id;
    }

    @Entity
    public static class ParentEntity {
        @Id
        @Getter
        @Setter
        private Long id;
    }

    public static class OfyService {
        static {
            try {
                ObjectifyService.register(ChildEntity.class);
                ObjectifyService.register(ParentEntity.class);
            } catch (Exception e) {
                System.out.println("Could not initialized objectify service." + e.toString());
            }
        }

        public static Objectify ofy() {
            return ObjectifyService.ofy();
        }

        public static ObjectifyFactory factory() {
            return ObjectifyService.factory();
        }
    }

    private static LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
    private static Closeable objectifyBegin;

    @BeforeClass
    public static void beforeClass(){
        helper.setUp();
        objectifyBegin = ObjectifyService.begin();
    }

    @AfterClass
    public static void afterClass(){
        objectifyBegin.close();
        helper.tearDown();
    }

    @Test
    public void testIdAllocation() {
        Ref<ParentEntity> parent1 = Ref.create(Key.create(ParentEntity.class, 1L));
        Ref<ParentEntity> parent2 = Ref.create(Key.create(ParentEntity.class, 2L));

        ChildEntity childEntity1 = new ChildEntity();
        childEntity1.setParent(parent1);
        childEntity1.setId(1L);

        ChildEntity childEntity2 = new ChildEntity();
        childEntity2.setParent(parent2);
        childEntity2.setId(1L);

        OfyService.ofy().save().entities(childEntity1, childEntity2).now();

        List<Key<ChildEntity>> keys = OfyService.ofy().load().type(ChildEntity.class).keys().list();
        // If overwriting occurred it would be only a single entity
        Assert.assertEquals(keys.size(), 2);
        for (Key<ChildEntity> child : keys) {

            System.out.println("Key( " +
                    "Key('" + child.getParent().getKind() + "'," + child.getParent().getId() + "), " +
                    "'" + child.getKind() + "', " + child.getId() + ")");
        }

        while(true) {
            KeyRange<ChildEntity> keyRangeParent1 = OfyService.factory().allocateIds(parent1, ChildEntity.class, 100);
            KeyRange<ChildEntity> keyRangeParent2 = OfyService.factory().allocateIds(parent2, ChildEntity.class, 100);

            for (Key<ChildEntity> keyParent1 : keyRangeParent1) {
                for (Key<ChildEntity> keyParent2 : keyRangeParent2) {
                    System.out.println(keyParent1.getId() + ", " + keyParent2.getId());
                    Assert.assertTrue(keyParent1.getId() != keyParent2.getId());
                }
            }
        }
    }
}

在开发服务器上,这个单元测试的输出是这样开始的

Key( Key('ParentEntity',1), 'ChildEntity', 1)
Key( Key('ParentEntity',2), 'ChildEntity', 1)
1, 101
1, 102
1, 103
1, 104
1, 105

这证明了两件事:

  1. 可以有不同祖先的相同 ID
  2. 开发服务器上的行为是 id 不会发生冲突(它们似乎使用相同的计数器)。基本上,这证明了我们无法通过查看开发服务器来证明一件事,但代码可以(理论上)在实时系统上运行。

警告:请不要部署此代码。那里有一个潜在的无限循环,实际命中的机会非常小。必须大幅增加分配的 id 数量并保留一个父级的分配 id 以进行比较。即便如此,您在测试所有已分配 ID 之前很久就会遇到 DeadlineExceeded 和 OutOfMemory 异常。

总结: 除非来自 Google 的人能告诉我们 id 分配是如何工作的,否则我们无法找到太多信息。快速查看Datastore implementation 表明分配是对数据存储的请求,因此没有可以分析的代码以进行更深入的挖掘。

我想我们只需要相信documentation 是正确的,当它说时

避免此类冲突的唯一方法是让您的应用程序使用 DatastoreService.allocateIds() 或 AsyncDatastoreService.allocateIds() 方法获取一个 ID 块。 数据存储区的自动 ID 生成器将跟踪 已经分配了这些方法,并将避免重复使用它们 另一个实体,因此您可以安全地使用此类 ID 而不会发生冲突。

关于id分配方式。

【讨论】:

  • 干得好。您发布的最后一句话的其余部分说:“避免此类冲突的唯一方法是让您的应用程序使用 DatastoreService.allocateIds() 或 AsyncDatastoreService.allocateIds() 方法获取一个 ID 块。”这两种方法都来自 @ 987654325@ 命名空间。希望该声明也适用于您和我都使用的com.googlecode.objectify.ObjectifyFactory 命名空间中的allocateId()/allocateIds() 方法。
  • 之所以这样做,是因为 Objectify 主要是用于执行对象关系映射的 Datastore API 的包装器(因此命名为 objectify)
  • 您是否认为拥有将实体保存到数据存储区并让它自动生成 ID 的方法和其他首先分配具有相同种类的 ID 并将其保存到数据存储区的方法是可以的?根据定义,allocateId() 是否确保它不会在数据存储区中为您提供 ID,即使它没有提供?只是想知道,因为在某些使用事务的 API 方法中,您可能需要分配一个 ID。
  • 是的。自动分配的 ID 和来自 allocateId() 的 ID不会发生冲突。
  • 开发服务器的工作方式与实际数据存储不同。我在自己的应用程序中发现了一种情况,其中两个具有不同父级的实例最终在实际数据存储中具有相同的 id,但在开发服务器上具有不同的 id。
猜你喜欢
  • 1970-01-01
  • 2016-02-15
  • 2013-03-03
  • 2013-01-30
  • 2011-10-16
  • 2019-10-07
  • 1970-01-01
  • 2022-08-08
相关资源
最近更新 更多