如果父实体不同,同一种类的两个实体可以具有相同的 ID 吗?

Can a two Entities of the same Kind have the same ID if the parent is different?

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

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

我为您编写了一个 JUnit 测试。它使用lombok,但你也可以写出getters和setters。

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

在devserver上,这个单元测试的输出是这样开始的

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. devserver 上的行为是 id 不会冲突(它们似乎使用相同的计数器)。基本上这证明我们无法通过查看开发服务器来证明一件事,但代码可以(理论上)在实时系统上是 运行。

警告:请不要部署此代码。那里有一个潜在的无限循环,实际命中的机会非常小。人们将不得不大幅增加分配的 ID 数量,并保留一个父代的分配 ID 以供比较。即使这样,您在测试所有分配的 ID 之前很久就会遇到 DeadlineExceeded 和 OutOfMemory 异常。

总结: 除非来自 Google 的人能启发我们了解 id 分配是如何工作的,否则我们能找到的东西不多。快速查看 Datastore implementation 表明分配是对数据存储的请求,因此没有可以分析的代码来深入挖掘。

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

The only way to avoid such conflicts is to have your application obtain a block of IDs with the methods DatastoreService.allocateIds() or AsyncDatastoreService.allocateIds(). The Datastore's automatic ID generator will keep track of IDs that have been allocated with these methods and will avoid reusing them for another entity, so you can safely use such IDs without conflict.

关于id分配方式