本文件著重說明如何針對 App Engine Datastore 查詢使用 Java 資料物件 (JDO) 持續性架構。如需一般的查詢相關資訊,請參閱 Datastore 查詢主頁面。
「查詢」會從 Datastore 中擷取符合一組特定條件的實體。查詢會針對指定種類的實體執行作業,其可指定實體屬性值、索引鍵和祖系的篩選條件,而且傳回的「結果」可能會是零個或更多個實體。另外,查詢也能指定排序順序,按照結果的屬性值進行排序。結果包含的所有實體都有篩選器中每個具名屬性的至少一個值 (有可能是空值),並按排序順序列出,其屬性值符合所有指定的篩選條件。查詢可傳回完整實體、 投影實體,或是僅傳回實體 金鑰。
典型的查詢包含以下幾項:
執行時,查詢會擷取符合所有指定篩選條件的特定種類實體,並按照指定的順序進行排序。查詢會以唯讀方式執行。附註:為了節省記憶體並提升效能,查詢應盡可能指定傳回的結果數限制。
附註:基於索引的查詢機制支援各種查詢,適用於大多數應用程式。不過,這種機制並不支援其他資料庫技術常見的某些查詢種類,Datastore 查詢引擎尤其不支援彙整與匯總查詢。如要瞭解 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()
清單中每個可能的值「組合」。
包含 not-equal (!=
) 或 contains()
運算子的單一查詢上限為 30 個子查詢。
如要進一步瞭解 !=
和 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");
如果未指定排序順序,則會按照從 Datastore 擷取的順序傳回結果。
附註:由於 Datastore 執行查詢的方式,如果查詢指定以不等式篩選條件篩選某個屬性,同時指定其他屬性的排序順序,則在不等式篩選條件中使用的屬性,順序必須在其他屬性之前。
範圍
查詢可指定傳回特定「範圍」的結果給應用程式。範圍會指出完整結果集中應最先及最後傳回的結果。系統會透過數字索引識別結果,其中 0
代表該組合中的首個結果。舉例來說,範圍為 5,
10
會傳回第 6 到第 10 個結果:
q.setRange(5, 10);
附註:使用範圍可能會影響效能,因為 Datastore 必須在擷取後捨棄開始位移之前的所有結果。舉例來說,範圍為 5,
10
的查詢會從 Datastore 擷取十個結果,捨棄前五個,並將剩下的五個傳回給應用程式。
以金鑰為基礎的查詢
實體金鑰可以是查詢篩選器或排序順序的主體。Datastore 會考量這類查詢的完整索引鍵值,包括實體的祖系路徑、類型,以及應用程式指派的索引鍵名稱字串或系統指派的數字 ID。由於系統中的所有實體都有專屬的鍵,因此您可以透過鍵查詢輕鬆擷取特定類型的實體,例如資料儲存庫內容的批次轉儲。與 JDOQL 範圍不同的是,這個方法不會因為實體數目的多寡而影響效率。
在比較不等式時,金鑰會根據以下標準進行排序:
- 祖系路徑
- 實體種類
- ID (索引鍵名稱或數字 ID)
祖系路徑元素的比較方式類似:依序先比較種類 (字串),再比較索引鍵名稱或數字 ID。種類和索引鍵名稱都是按位元組值排序的字串;數字 ID 是按數字排序的整數。若父項和種類相同的實體混用索引鍵名稱和數字 ID,則會先列出採用數字 ID 的實體,再列出採用索引鍵名稱的實體。
在 JDO 中,您可以透過物件的主要金鑰欄位參考查詢的實體金鑰。如要將鍵用作查詢篩選器,請將參數類型 Key
指定給
declareParameters()
方法。以下程式碼可以搜尋最喜愛某種食物的所有 Person
實體,並假設 Person
與 Food
之間存在
一對一非從屬關聯性:
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
例項使用設定時發出的所有呼叫,設定讀取政策 (同步一致性與最終一致性) 和 Datastore 呼叫期限。您也可以覆寫個別 Query
物件的這些選項。(但請注意,如果您透過金鑰擷取實體,則無法覆寫這些選項的設定。)
為 Datastore 查詢選取最終一致性後,用於收集結果的索引也會透過最終一致性的方式存取。查詢有時會傳回不符合查詢條件的實體,即使採用同步一致的讀取政策也是如此。(如果查詢使用祖系篩選器,您可以使用交易來確保結果集一致。)
如要覆寫單一查詢的讀取政策,請呼叫其
addExtension()
方法:
Query q = pm.newQuery(Person.class); q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");
可能的值為 "EVENTUAL"
和 "STRONG"
;預設值為 "STRONG"
,除非在設定檔 jdoconfig.xml
中另行設定。
如要覆寫單一查詢的 Datastore 呼叫期限,請呼叫其
setDatastoreReadTimeoutMillis()
方法:
q.setDatastoreReadTimeoutMillis(3000);
值為以毫秒為單位表示的時間間隔。