Objectify从webSafeKey创建Key忽略databaseID的单元测试问题
问题背景
我在给一个用Objectify从Datastore加载实体的函数写单元测试,为了避开生产环境的Default数据库,专门创建了Test数据库并导入了Default的数据,本地测试的Datastore配置也指向Test库。
待测试函数接收webSafeKey(编码后的实体密钥)参数,通过Key.create()创建Key对象,执行的查询代码如下:
ofy().load().type(Entity.class).ancestor(akey).filterKey(key).first().now();
但用LocalDatastoreService本地运行测试时,抛出异常:
DatastoreException: mismatched databases within request...
排查后发现,即便传入的webSafeKey包含databaseId,Key.create()生成的Key对象里的databaseId还是为空,导致密钥指向Default库,触发了数据库不匹配的异常。
我试过从webSafeKey里提取ID直接查询,但生成的Key缺少祖先信息,直接报空指针异常(NPE)。
我的问题:
- 能不能在Key对象创建后设置其databaseId?
- 怎么测试从webSafeKey加载Test数据库中的实体?
问题解答
1. 能否在Key对象创建后设置databaseId?
不行。Objectify的Key对象是不可变的,一旦创建就无法修改其属性(包括databaseId)。这是设计上的限制,目的是保证Key的唯一性和不可变性。
2. 测试从webSafeKey加载Test库实体的解决方案
有两种可靠的方式解决这个问题:
方案一:手动解析webSafeKey并重新构建带databaseId的Key
借助Datastore原生的KeyFactory解析webSafeKey,获取所有组件(包括祖先路径、ID/名称、种类等),手动指定databaseId重新构建Key:
// 解析webSafeKey com.google.cloud.datastore.Key rawKey = com.google.cloud.datastore.KeyFactory.createFromUrlSafe(webSafeKey); // 构建带Test数据库ID的KeyFactory com.google.cloud.datastore.KeyFactory keyFactory = datastore.newKeyFactory() .setDatabaseId("Test") .setKind(rawKey.getKind()); // 处理祖先路径 com.google.cloud.datastore.Key.Builder keyBuilder = keyFactory.newKeyBuilder(rawKey.getNameOrId()); if (rawKey.hasParent()) { // 递归构建多层祖先Key com.google.cloud.datastore.Key parent = rawKey.getParent(); com.google.cloud.datastore.KeyFactory parentFactory = datastore.newKeyFactory() .setDatabaseId("Test") .setKind(parent.getKind()); com.google.cloud.datastore.Key parentKey = parentFactory.newKey(parent.getNameOrId()); while (parent.hasParent()) { parent = parent.getParent(); parentFactory = datastore.newKeyFactory() .setDatabaseId("Test") .setKind(parent.getKind()) .setParent(parentKey); parentKey = parentFactory.newKey(parent.getNameOrId()); } keyBuilder.setParent(parentKey); } com.google.cloud.datastore.Key testDbKey = keyBuilder.build(); // 转换为Objectify的Key Key<Entity> objectifyKey = Key.create(testDbKey);
之后用这个objectifyKey执行查询,就能正确指向Test数据库。
方案二:修改LocalDatastoreService的默认数据库ID
如果所有测试用例都针对Test数据库,可以直接修改LocalDatastoreService的配置,把默认数据库ID设为"Test",这样所有Key都会默认使用这个数据库ID:
// 在测试初始化阶段配置LocalDatastoreService LocalDatastoreServiceConfig config = LocalDatastoreServiceConfig.newBuilder() .setDefaultDatabaseId("Test") .build(); LocalServiceTestHelper helper = new LocalServiceTestHelper( new LocalDatastoreServiceTestConfig().setServiceConfig(config) ); helper.setUp();
这种方式更简洁,适合全测试链路都指向Test库的场景。
内容的提问来源于stack exchange,提问作者Astrid Perdomo

