JDO 中的 Datastore 查詢

本文件著重說明如何針對 App Engine Datastore 查詢使用 Java 資料物件 (JDO) 持續性架構。如需一般的查詢相關資訊,請參閱 Datastore 查詢主頁面。

「查詢」會從 Cloud Datastore 中擷取符合一組特定條件的實體。查詢可針對指定種類的實體執行作業,並針對實體的屬性值、金鑰與祖系指定篩選器,再傳回 0 個以上的實體來當做「結果」。 另外,查詢也能指定排序順序,按照結果的屬性值進行排序。結果包含的所有實體都有篩選器中每個具名屬性的至少一個值 (有可能是空值),並按排序順序列出,其屬性值符合所有指定的篩選條件。查詢可傳回完整實體、投影實體,或是僅傳回實體金鑰

典型的查詢包含以下幾項:

執行時,查詢會擷取符合所有指定篩選條件的特定種類實體,並按照指定的順序進行排序。查詢會以唯讀方式執行。

附註:為了節省記憶體並提升效能,查詢應盡可能指定傳回的結果數限制。

附註:基於索引的查詢機制支援各種查詢,適用於大多數應用程式。不過這種機制並不支援其他資料庫技術常見的某些查詢種類,Cloud Datastore 查詢引擎尤其不支援彙整與匯總查詢。如要瞭解 Cloud Datastore 查詢的限制,請參閱 Datastore 查詢頁面。

使用 JDOQL 查詢

JDO 具有一套查詢語言,用於擷取符合一組特定條件的物件。這種語言稱為 JDOQL,它會直接參照 JDO 資料類別和欄位,並且提供查詢參數和結果的類型檢查。JDOQL 與 SQL 類似,但更適合用於 App Engine Datastore 這類的物件導向資料庫 (App Engine 的 JDO API 部署項目並不直接支援 SQL 查詢)。

JDO Query 介面支援多種呼叫樣式:您可以使用 JDOQL 字串語法在字串中指定完整查詢,也可以呼叫用於 Query 物件的方法來指定部分或全部查詢。下列範例顯示了呼叫方法樣式,當中包含一個篩選器與一個排序順序,並針對篩選器中使用的值使用替代參數。傳送至 Query 物件 execute() 方法的引數值會按照指定順序替代至查詢:

import java.util.List;
import javax.jdo.Query;

// ...

Query q = pm.newQuery(Person.class);
q.setFilter("lastName == lastNameParam");
q.setOrdering("height desc");
q.declareParameters("String lastNameParam");

try {
  List<Person> results = (List<Person>) q.execute("Smith");
  if (!results.isEmpty()) {
    for (Person p : results) {
      // Process result p
    }
  } else {
    // Handle "no results" case
  }
} finally {
  q.closeAll();
}

下方是使用字串語法的相同查詢:

Query q = pm.newQuery("select from Person " +
                      "where lastName == lastNameParam " +
                      "parameters String lastNameParam " +
                      "order by height desc");

List<Person> results = (List<Person>) q.execute("Smith");

您可以混合這些定義查詢的樣式,例如:

Query q = pm.newQuery(Person.class,
                      "lastName == lastNameParam order by height desc");
q.declareParameters("String lastNameParam");

List<Person> results = (List<Person>) q.execute("Smith");

您可以多次呼叫 execute() 方法,重複使用單一 Query 執行個體,將參數取代為不同的值。每個呼叫都會執行查詢並傳回結果的集合。

JDOQL 字串語法支援以字串與數值指定常值;其他所有值類型都必須使用參數替代。查詢字串內的常值可括在單引號 (') 或雙引號 (") 內。以下是使用字串常值的範例:

Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' order by height desc");

篩選器

「屬性篩選器」指定

  • 屬性名稱
  • 比較運算子
  • 屬性值
例如:

Query q = pm.newQuery(Person.class);
q.setFilter("height <= maxHeight");

屬性值必須由應用程式提供,不能參照其他屬性或以其他屬性計算得出。如果實體具有指定名稱的屬性,且屬性值與篩選器中指定的值比較,符合比較運算子描述的方式,則該實體符合篩選器的條件。

比較運算子可以為以下任何一種:

運算子 意義
== 等於
< 小於
<= 小於或等於
> 大於
>= 大於或等於
!= 不等於

查詢主頁面中所述,單一查詢無法針對多項屬性使用不等式篩選器 (<<=>>=!=),但允許針對相同屬性使用多個不等式篩選器,例如用於查詢某個範圍的值。contains() 篩選器可對應至 SQL 中的 IN 篩選器,系統則會使用下列語法支援這些篩選器:

// Query for all persons with lastName equal to Smith or Jones
Query q = pm.newQuery(Person.class, ":p.contains(lastName)");
q.execute(Arrays.asList("Smith", "Jones"));

不等於 (!=) 運算子實際上會執行兩項查詢:一項查詢中的其他篩選器皆未變更,「不等於」篩選器會替換為「小於」(<) 篩選器,另一項則是會替換為「大於」(>) 篩選器,然後系統會將結果合併並排序。查詢中只能有一個「不等於」篩選器,且不能有任何其他的不等式篩選器。

contains() 運算子也會執行多個查詢:針對指定清單中的各個項目各有一個查詢,而所有其他篩選器均未變更,且 contains() 篩選器替換為「等於」(==) 篩選器。結果會依照清單中項目的順序進行合併。如果查詢中包含多個 contains() 篩選器,則會以多項查詢執行,每項查詢會對應至 contains() 清單中每個可能的值「組合」

單一查詢包含不等於 (!=) 或上限為 30 項子查詢的 contains() 運算子。

如要進一步瞭解 !=contains() 查詢如何翻譯為 JDO/JPA 架構中的多項查詢,請參閱使用 != 和 IN 篩選器進行查詢一文。

在 JDOQL 字串語法中,您可以使用 && (邏輯「and」) 與 || (邏輯「or」) 運算子分隔多個篩選器:

q.setFilter("lastName == 'Smith' && height < maxHeight");

系統不支援否定 (邏輯「not」) 運算子。另請注意,|| 運算子只能在其分隔的所有篩選器具有相同屬性名稱時 (即其可以合併為單一 contains() 篩選器時) 使用:

// Legal: all filters separated by || are on the same property
Query q = pm.newQuery(Person.class,
                      "(lastName == 'Smith' || lastName == 'Jones')" +
                      " && firstName == 'Harold'");

// Not legal: filters separated by || are on different properties
Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' || firstName == 'Harold'");

排序順序

查詢「排序順序」指定

  • 屬性名稱
  • 排序方向 (遞增或遞減)

例如:

例如:

// Order alphabetically by last name:
Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc");

// Order by height, tallest to shortest:
Query q = pm.newQuery(Person.class);
q.setOrdering("height desc");

如果查詢包含多個排序順序,他們將按照指定的順序排序。下列範例會先將「last name」遞增排列,再將「height」遞減排列:

Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc, height desc");

如果未指定排序順序,則會按照從 Cloud Datastore 擷取的順序傳回結果。

附註:由於 Cloud Datastore 執行查詢的方式所致,若查詢指定以不等式篩選條件對其他屬性進行排序,則在不等式篩選中使用的屬性必須排序在其他屬性之前。

範圍

查詢可指定傳回特定「範圍」的結果給應用程式。範圍會指出完整結果集中應最先及最後傳回的結果。結果由其數值索引識別,其中 0 表示集合中的第一個結果。例如,5, 10 範圍會傳回第 6 個到第 10 個結果:

q.setRange(5, 10);

附註:使用範圍可能會影響效能,因為 Datastore 必須在擷取後捨棄開始位移之前的所有結果。例如,5, 10 範圍的查詢會從 Datastore 擷取十個結果,捨棄前五個,然後將剩下五個傳回給應用程式。

以金鑰為基礎的查詢

實體金鑰可以是查詢篩選器或排序順序的主體。Datastore 會考慮此類查詢的完整金鑰值,其中包括實體的祖系路徑、種類與應用程式指派的金鑰名稱字串或系統指派的數字 ID。由於金鑰在系統的所有實體中不得重複,因此金鑰查詢可讓您分批輕鬆擷取指定種類的實體,例如 Datastore 內容的批次傾印。與 JDOQL 範圍不同的是,這個方法不會因為實體數目的多寡而影響效率。

在比較不等式時,金鑰會根據以下標準進行排序:

  1. 祖系路徑
  2. 實體種類
  3. ID (金鑰名稱或數字 ID)

祖系路徑的元素會以類似的方式進行比較:先比較種類 (字串),再比較金鑰名稱或數字 ID。種類和金鑰名稱都是字串,且會按照位元組值排序;數字 ID 為整數,會按照數值順序排序。若具有相同父級和種類的實體使用金鑰名稱字串與數字型 ID 的混合,數字型 ID 必須置於金鑰名稱之前。

在 JDO 中,您可以透過物件的主要金鑰欄位參考查詢的實體金鑰。如要使用金鑰來當做查詢篩選器,您可以指定 declareParameters() 方法的參數類型 Key。以下程式碼可以搜尋最喜愛某種食物的所有 Person 實體,並假設 PersonFood 之間存在一對一非從屬關聯性

Food chocolate = /*...*/;

Query q = pm.newQuery(Person.class);
q.setFilter("favoriteFood == favoriteFoodParam");
q.declareParameters(Key.class.getName() + " favoriteFoodParam");

List<Person> chocolateLovers = (List<Person>) q.execute(chocolate.getKey());

「僅有金鑰的查詢」僅傳回結果實體的金鑰而非實體本身,與擷取整個實體相比,延遲及成本都較低:

Query q = pm.newQuery("select id from " + Person.class.getName());
List<String> ids = (List<String>) q.execute();

先執行僅有金鑰的查詢,並從結果中獲取實體的子集,通常較為經濟實惠。您不需執行一般查詢而得到多餘的實體。

Extent

JDO「extent」代表 Datastore 中屬於特定類別的每個物件。您可以將所需類別傳送至持續性管理程式的 getExtent() 方法來建立 extent。Extent 介面可以擴充 Iterable 介面來存取結果,並視需求分批擷取結果。存取完結果之後,您就能呼叫 extent 的 closeAll() 方法。

下列範例逐一處理 Datastore 中的每個 Person 物件:

import java.util.Iterator;
import javax.jdo.Extent;

// ...

Extent<Person> extent = pm.getExtent(Person.class, false);
for (Person p : extent) {
  // ...
}
extent.closeAll();

依查詢刪除實體

如果您發出的查詢目標是刪除符合查詢篩選器的所有實體,可以使用 JDO 的「依查詢刪除」功能少編寫一些程式碼。以下程式碼會刪除身高超過指定高度的所有人:

Query q = pm.newQuery(Person.class);
q.setFilter("height > maxHeightParam");
q.declareParameters("int maxHeightParam");
q.deletePersistentAll(maxHeight);

您會發現此處的差異僅在於我們呼叫的是 q.deletePersistentAll(),而非 q.execute()。上方提及的所有篩選器、排序順序與索引規則和限制均適用於選取或刪除結果集的查詢。不過請注意,就像您已使用 pm.deletePersistent() 刪除這些 Person 實體一樣,查詢所刪除實體的任何相依子項也會遭到刪除。如要進一步瞭解相依子項,請參閱 JDO 中的實體關聯性頁面。

查詢游標

在 JDO 中,您可以藉由擴充功能與 JDOCursorHelper 類別來將游標與 JDO 查詢搭配使用。擷取清單形式的結果或使用疊代器時可使用游標。如要取得游標,您可以將結果清單或疊代器傳送至靜態方法 JDOCursorHelper.getCursor()

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jdo.Query;
import com.google.appengine.api.datastore.Cursor;
import org.datanucleus.store.appengine.query.JDOCursorHelper;

Query q = pm.newQuery(Person.class);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the first 20 results

Cursor cursor = JDOCursorHelper.getCursor(results);
String cursorString = cursor.toWebSafeString();
// Store the cursorString

// ...

// Query q = the same query that produced the cursor
// String cursorString = the string from storage
Cursor cursor = Cursor.fromWebSafeString(cursorString);
Map<String, Object> extensionMap = new HashMap<String, Object>();
extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
q.setExtensions(extensionMap);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the next 20 results

如要進一步瞭解查詢游標,請參閱 Datastore 查詢頁面。

Datastore 的讀取政策和呼叫期限

您可以使用設定為 PersistenceManager 執行個體發出的所有呼叫設定讀取政策 (同步一致性與最終一致性) 和資料儲存庫呼叫期限。您也可以覆寫個別 Query 物件的這些選項。(但請注意,如果您透過金鑰擷取實體,則無法覆寫這些選項的設定。)

當您為 Datastore 查詢選取最終一致性時,就同樣將透過最終一致性,存取查詢用來收集結果的索引。查詢有時會傳回不符合查詢條件的實體,但使用同步一致性讀取政策時也會如此。(如果查詢使用祖系篩選器,您可以使用交易確保一致的結果集。)如要進一步瞭解實體和索引的更新方式,請參閱 App Engine 中的交易隔離一文。

如要覆寫單一查詢的讀取政策,請呼叫其 addExtension() 方法:

Query q = pm.newQuery(Person.class);
q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

可能的值為 "EVENTUAL""STRONG";除非在設定檔 jdoconfig.xml 中設定,否則預設為 "STRONG"

如要覆寫單一查詢的 Datastore 呼叫期限,請呼叫其 setDatastoreReadTimeoutMillis() 方法:

q.setDatastoreReadTimeoutMillis(3000);

值為以毫秒為單位表示的時間間隔。

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Java 適用的 App Engine 標準環境