我为你写了一个 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
这证明了两件事:
- 可以有不同祖先的相同 ID
- 开发服务器上的行为是 id 不会发生冲突(它们似乎使用相同的计数器)。基本上,这证明了我们无法通过查看开发服务器来证明一件事,但代码可以(理论上)在实时系统上运行。
警告:请不要部署此代码。那里有一个潜在的无限循环,实际命中的机会非常小。必须大幅增加分配的 id 数量并保留一个父级的分配 id 以进行比较。即便如此,您在测试所有已分配 ID 之前很久就会遇到 DeadlineExceeded 和 OutOfMemory 异常。
总结:
除非来自 Google 的人能告诉我们 id 分配是如何工作的,否则我们无法找到太多信息。快速查看Datastore implementation 表明分配是对数据存储的请求,因此没有可以分析的代码以进行更深入的挖掘。
我想我们只需要相信documentation 是正确的,当它说时
避免此类冲突的唯一方法是让您的应用程序使用 DatastoreService.allocateIds() 或 AsyncDatastoreService.allocateIds() 方法获取一个 ID 块。
数据存储区的自动 ID 生成器将跟踪
已经分配了这些方法,并将避免重复使用它们
另一个实体,因此您可以安全地使用此类 ID 而不会发生冲突。
关于id分配方式。