Datastore 查询

Datastore 查询会从 Cloud Datastore 中检索满足一组指定条件的实体

典型的查询包括以下内容:

执行时,查询会检索给定种类的实体中满足所有给定过滤条件的所有实体,并按指定顺序排序。查询以只读方式执行。

此页面介绍了 App Engine 中用于从 Cloud Datastore 中检索数据的查询的结构和类型。

过滤条件

查询的过滤条件用于对待检索实体的属性祖先实体设定限制。

属性过滤条件

属性过滤条件指定以下三项:

  • 属性名称
  • 比较运算符
  • 属性值
例如:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

属性值必须由应用提供;不能引用其他属性或者根据其他属性进行计算。如果实体具有给定名称的属性,并且其值与过滤条件中指定值的比较结果与比较运算符所述的方式一致,则该实体满足该过滤条件。

比较运算符可以是以下任何一项(定义为嵌套类 Query.FilterOperator 中的枚举常量):

运算符 含义
EQUAL 等于
LESS_THAN 小于
LESS_THAN_OR_EQUAL 小于或等于
GREATER_THAN 大于
GREATER_THAN_OR_EQUAL 大于或等于
NOT_EQUAL 不等于
IN 成员(等于指定列表中的任意值)

NOT_EQUAL 运算符实际上执行两次查询:一次查询中,NOT_EQUAL 过滤条件替换为 LESS_THAN 过滤条件且所有其他过滤条件均不变;另一次查询中,该过滤条件替换为 GREATER_THAN 过滤条件。所得结果随后按顺序合并。一个查询最多只能有一个 NOT_EQUAL 过滤条件,已有一个不等性过滤条件的查询不能再有任何其他不等性过滤条件。

IN 运算符也执行多次查询:指定列表中的每一项对应一次查询,此时 IN 过滤条件替换为 EQUAL 过滤条件且所有其他过滤条件均不变。结果按列表中内容的顺序合并。如果查询有多个 IN 过滤条件,那么可以作为多个查询执行,每个查询对应于 IN 列表中每种可能的值组合

包含 NOT_EQUALIN 运算符的单个查询不得超过 30 个子查询。

如需详细了解 NOT_EQUALIN 查询在 JDO/JPA 框架中如何转化为多个查询,请参阅使用 != 和 IN 过滤条件进行查询一文。

键过滤条件

如需将实体键的值作为过滤条件,请使用特殊属性 Entity.KEY_RESERVED_PROPERTY

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

您还可以按 Entity.KEY_RESERVED_PROPERTY 进行升序排序。

在进行不等性比较时,键会依次按照以下标准排序:

  1. 祖先实体路径
  2. 实体种类
  3. 标识符(键名称或数字 ID)

祖先实体路径的元素采用类似的方式进行比较:按种类(字符串),然后按键名称或数字 ID 比较。种类和键名称是字符串,按字节值排序;数字 ID 是整数,按数字顺序排列。如果具有相同父级和种类的实体混合使用键名称字符串和数字 ID,那么使用数字 ID 的实体排在使用键名称的实体前面。

针对键的查询就像针对属性的查询一样使用索引,并且在相同情况下需要自定义索引,但有一些例外:不等性过滤条件或键的升序排序不需要自定义索引,但键的降序排序则需要自定义索引。与所有查询一样,测试需要自定义索引的查询时,开发 Web 服务器会在索引配置文件中创建相应条目。

祖先过滤条件

您可以对 Datastore 查询设置过滤条件以便仅查询指定的祖先实体,从而使返回的结果仅包含来自该祖先实体的实体:

Query q = new Query("Person").setAncestor(ancestorKey);

特殊查询类型

应特别注意以下特定类型的查询:

不分类查询

没有种类、没有祖先实体的查询会从 Datastore 中检索某个应用的所有实体。这包括由其他 App Engine 功能创建和管理的实体,例如统计信息实体Blobstore 元数据实体(若有)。这种“不分类查询”不能包含针对属性值的过滤条件或排序顺序。但是,此类查询可以指定 Entity.KEY_RESERVED_PROPERTY 作为属性名称对实体键进行过滤:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

祖先查询

设有祖先过滤条件的查询将其结果限制为指定实体及其后代:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

不分类祖先查询

包含祖先过滤条件的不分类查询将检索指定的祖先及其所有后代,不管其属于哪种类型。此类查询无需自定义索引。跟所有不分类查询一样,其不能包含针对属性值的过滤条件或排序顺序,但可以过滤实体的键:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

以下示例说明了如何检索来自给定祖先的所有实体:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

仅限于键的查询

“仅限于键的查询”仅返回结果实体的键而非实体本身,与检索整个实体相比,延迟时间更短,费用更低:

Query q = new Query("Person").setKeysOnly();

执行常规查询获取的实体数量可能超过所需数量,因此首先执行仅限于键的查询,然后从结果中获取实体的子集的做法往往更为经济。

投影查询

有时,您在查询结果中真正需要的只是一些特定属性的值。在这种情况下,您可以使用投影查询仅检索您实际上关注的属性,与检索整个实体相比,这种做法的延迟时间更短、费用更低。如需了解详情,请参阅投影查询页面。

排序顺序

查询“排序顺序”需指定以下两项:

  • 属性名称
  • 排序方向(升序或降序)

例如:

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

如果查询包含多个排序顺序,则按指定的顺序加以应用。以下示例先按姓升序排序,然后按身高降序排序:

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

如果未指定排序顺序,将按照从 Datastore 检索的顺序返回结果。

注意:由于 Datastore 执行查询的方式,如果查询对某个属性指定了不等性过滤条件,而对其他属性指定了排序顺序,则不等性过滤条件中使用的属性必须先于其他属性进行排序。

索引

每个 Datastore 查询使用一个或多个索引来计算其结果,索引包含索引属性指定的序列中的实体键,还可以选择包含实体的祖先实体。索引会逐渐更新以体现应用对其实体所做的更改,从而保证无需进一步计算即可提供所有查询的正确结果。

App Engine 会为实体的每个属性预定义简单索引。App Engine 应用可以在名为 datastore-indexes.xml索引配置文件中定义更多的自定义索引,该文件在应用的 /war/WEB-INF/appengine-generated 目录中生成。开发服务器在遇到无法使用现有索引执行的查询时,会自动向此文件添加建议。在上传应用之前,可以通过编辑该文件来手动优化索引。

查询接口示例

低级 Java Datastore API 提供用于构建查询的类 Query,以及用于从 Datastore 中检索实体的 PreparedQuery 接口:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

请注意使用 FilterPredicateCompositeFilter 来构建过滤条件。如果您在查询中只设置一个过滤条件,则可以单独使用 FilterPredicate

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

但是,如果要对查询设置不止一个过滤条件,则必须使用 CompositeFilter,其需要至少两个过滤条件。上述示例使用了快捷方式帮助程序 CompositeFilterOperator.and;以下示例展示了构造复合 OR 过滤条件的一种方法:

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

后续步骤