Google Code 提供下列語言介面: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
每個資料存放區查詢都會使用索引,而索引是一個表格,當中列有依需求排序的查詢結果。「應用服務引擎」應用程式會在名稱為 datastore-indexes.xml 的設定檔中定義其索引。 當開發網頁伺服器遇到尚未設定索引的查詢時,可以自動為這個檔案產生建議。
索引式查詢機制支援大部分常見的查詢種類,但是可能不支援您在其他資料庫技術中使用的部分查詢。查詢的限制及其解釋如下所述。
查詢會從資料存放區抓取符合一組條件的實體。查詢會指定實體種類,根據實體屬性值的零或多個條件 (有時候稱為「篩選器」),以及零或多個排序順序描述。執行查詢時,它會擷取符合指定條件之指定種類的所有實體,以描述的順序排序。
JDO 可以查詢符合特定條件的實體。您可以使用 JDO Extent 代表某個種類所有實體 (某個類別所有物件) 的集合。
JDO 所提供的查詢語言可以擷取符合一系列條件的物件。這個查詢語言的名稱為 JDOQL,可直接參考 JDO 資料類別和欄位,並提供查詢參數和結果的類型檢查。JDOQL 與 SQL 類似,但是更適合用於物件導向的資料庫,例如「應用服務引擎」資料存放區。(「應用服務引擎」資料存放區不支援使用 JDO 介面執行 SQL 查詢)。
查詢 API 支援多種呼叫樣式。您可以使用 JDOQL 字串語法,在一個字串中指定完整的查詢。您也可以呼叫查詢物件上的方法,指定部分或完整的查詢。
下方為透過呼叫方法樣式查詢的簡單範例,當中將採用一個篩選器和一個排序順序,並使用參數取代篩選器所使用的值。這個範例會使用查詢的取代值呼叫 Query 物件的 execute() 方法,並遵循這些值的宣告順序逐一呼叫。
import java.util.List;
import javax.jdo.Query;
// ...
Query query = pm.newQuery(Employee.class);
query.setFilter("lastName == lastNameParam");
query.setOrdering("hireDate desc");
query.declareParameters("String lastNameParam");
try {
List<Employee> results = (List<Employee>) query.execute("Smith");
if (results.iterator().hasNext()) {
for (Employee e : results) {
// ...
}
} else {
// ... no results ...
}
} finally {
query.closeAll();
}
下方是使用字串語法的相同查詢:
Query query = pm.newQuery("select from Employee " +
"where lastName == lastNameParam " +
"order by hireDate desc " +
"parameters String lastNameParam")
List<Employee> results = (List<Employee>) query.execute("Smith");
您可以混合這些定義查詢的樣式。例如:
Query query = pm.newQuery(Employee.class,
"lastName == lastNameParam order by hireDate desc");
query.declareParameters("String lastNameParam");
List<Employee> results = (List<Employee>) query.execute("Smith");
您可以呼叫 execute() 方法多次,以重複使用單一 Query 實例,並使用不同數值取代參數。每一個呼叫都會執行查詢,再傳回結果的集合。
JDOQL 字串語法支援在字串值和數字值使用值文字。用單引號 (') 或雙引號 (") 括住字串,所有其他的值類型則必須使用參數取代。下方為使用字串文字值的範例:
Query query = pm.newQuery(Employee.class,
"lastName == 'Smith' order by hireDate desc");
「篩選器」會指定欄位名稱、運算子和值。值必須由應用程式提供;它無法參考另一個屬性,或使用其他屬性計算。運算子可以是下列任何一種:< <= == >= >
注意:Java 資料存放區不支援在 Python 資料存放區介面中實作的 != 和 IN 篩選運算子。(在 Python 介面中,這些運算子是以多個資料存放區查詢在用戶端程式庫中完成實作;它們不是資料存放區的既有功能)。
篩選器的主體可以是任何物件欄位,包括主要金鑰和實體群組父系 (請參閱「交易」)。
實體必須符合所有篩選器,才能成為結果。JDOQL 字串語法會使用 && (「and」邏輯) 區隔指定多個篩選器,但是不支援其他的篩選器邏輯組合 (「or」、「not」邏輯)。
受限於「應用服務引擎」資料存放區的查詢執行方式,單一查詢不能在多個屬性上使用不等式篩選器 (< <= >= >)。JDOQL 允許在相同屬性上使用多個不等式篩選器 (例如,查詢某個範圍的值)。請參閱「查詢的限制」。
query.setFilter("lastName == 'Smith' && hireDate > hireDateMinimum");
query.declareParameters("Date hireDateMinimum");
「排序順序」可指定屬性和方向 (遞增或遞減)。傳回的結果會按照指定時的順序,逐一使用指定順序排序。如果查詢沒有指定排序順序,結果將依實體金鑰排序。
受限於「應用服務引擎」資料存放區的查詢執行方式,如果查詢在某個屬性上指定不等式篩選器,並在其他屬性上指定排序順序,則使用不等式篩選器的屬性必須在其他屬性「之前」先行排序。請參閱「查詢的限制」。
query.setOrdering("hireDate desc, firstName asc");
查詢可以指定傳回特定「範圍」的結果給應用程式。範圍可在完整結果集中,指定要傳回的第一個結果,以及要傳回的最後一個結果,方法是使用數字索引,並以 0 代表第一個結果。例如,5, 10 的範圍會傳回第 6、第 7、第 8、第 9 和第 10 個結果。
起始位移隱含效能問題:資料存放區必須在起始位移之前,擷取然後捨棄所有結果。例如,5, 10 的範圍會從資料存放區擷取 10 個結果,然後捨棄前面 5 個,再將剩餘的 5 個傳回應用程式。
query.setRange(5, 10);
JDO Extent 代表特定類別的資料存放區中的每個物件。
您可以使用 PersistenceManager 的 getExtent() 方法起始 Extent,將它傳送給資料類別。Extent 類別會實作可重複執行介面來存取結果。當您完成結果存取時,可以呼叫 closeAll() 方法。
下方範例會在資料存放區的每個 Employee 物件上重複執行:
import java.util.Iterator;
import javax.jdo.Extent;
// ...
Extent extent = pm.getExtent(Employee.class, false);
for (Employee e : extent) {
// ...
}
extent.closeAll();
Extent 會批次擷取結果,且可以超越查詢的 1,000 個結果限制。
對於每個應用程式執行的查詢動作,「應用服務引擎」資料存放區都會編出索引。如果應用程式變更了資料存放區實體,資料存放區就會以正確的結果來更新索引。應用程式執行查詢時,資料存放區會直接從相應的索引擷取結果。
針對查詢中使用的種類、篩選器屬性和運算子、排序順序的組合,應用程式都有索引。請參考 JDOQL 當中的查詢範例:
select from Person where lastName == "Smith"
&& height < 72
order by height desc
此查詢的索引是 Person 種類之實體的金鑰表格,而欄中是 height 的值和 lastName 的屬性。索引會按照 height 以遞減方式排序。
相同格式,但不同篩選值的兩個查詢,會使用相同的索引。例如,下列查詢使用的索引,與上述的查詢相同:
select from Person where lastName == "Jones"
&& height < 64
order by height desc
資料存放區使用下列步驟來執行查詢:
索引表格包含篩選器或排序順序所使用之每個屬性的欄。列會按照下列各項依順序排序:
這樣會將使用此索引之每個可能查詢的所有結果,放在表格中的連續列中。
此機制支援的查詢種類十分廣泛,因此也適合大部分應用程式。不過,此機制並不支援您在其他資料庫技術所慣用的部分查詢種類。
實體必須有索引所參考的全部屬性,才會包含在索引中。如果實體沒有索引所參考的屬性,則實體不會出現在索引中,也就不可能會出現在使用該索引的查詢結果。
請注意,「應用服務引擎」資料存放區會區別沒有屬性以及屬性值為空值的實體 (null)。如果您要查詢同一種類的所有實體,可以使用JDO 或 JPA 資料類別,這些資料類別會為每個類別欄位的對應屬性指派屬性值。
長文字類型 (Text) 或大型二進位類型 (Blob) 屬性值的屬性不會納入索引,因此無法提供查詢。
不會索引這些屬性值的結果,就是在屬性使用篩選器或排序順序的查詢,不會比對屬性值為 Text 或 Blob 的實體。具有這類值的屬性,就像是設定與查詢篩選器和排序順序無關。
如果兩個實體的屬性擁有相同名稱,但是屬性值的類型不同,則屬性的索引會先依照屬性值類型排序實體,再依照每個類型適用的順序排序。舉例來說,如果兩個實體均擁有「年齡」屬性,但是其中一個的屬性值為整數,而另一個的屬性值為字串,則使用「年齡」屬性排序時,擁有整數值的實體會排在擁有字串值的實體前面,無論兩者屬性值為何。
當兩個實體的屬性值分別為整數和浮點數時,請特別留意,因為這兩種類型的差別較難以辨識,可是在資料存放區中確實為不同的類型。整數的順序比浮點高,因此擁有整數值 38 的屬性會排在擁有浮點值 37.5 的屬性前面。
(如果您使用的是 JDO 或 JPA,則不會出現此情形;但如果您尚未更新現有實體即修改欄位類型,或是使用低階 Datastore API (資料存放區 API) 或非 Java API,則可能出現此情形。)
「應用服務引擎」按照預設,會為每個簡單的查詢建置索引。針對其他查詢,應用程式必須在設定檔中指定它需要的索引,此設定檔的名稱為 datastore-indexes.xml。 若「應用服務引擎」下的應用程式嘗試執行查詢,卻沒有相應的索引 (不論是預設所提供或 datastore-indexes.xml 所描述的),則查詢將會失敗。
「應用服務引擎」會為下列格式的查詢提供自動索引:
其他型式的查詢都必須在 datastore-indexes.xml 中指定其索引,包括:
開發網頁伺服器使得索引設定的管理更加輕鬆:需要索引的查詢如果沒有索引將無法執行,但是開發網頁伺服器可以產生索引的設定檔,成功地執行查詢。本機測試應用程式時,如果呼叫了應用程式可能執行的所有查詢 (各種種類、上階、篩選器和排序順序的組合),則產生的項目將代表一組完整的索引。如果測試並未執行每種可能的查詢格式,您可以在上傳應用程式之前,檢視並修改索引的設定檔。
您可以使用 datastore-indexes.xml 設定檔 (位於應用程式 WAR 的 WEB-INF/ 目錄) 手動定義索引。如果您已啟用自動索引設定 (請參閱下方),開發伺服器將在 WEB-INF/appengine-generated/ 目錄下的 datastore-indexes-auto.xml 檔案中建立索引設定,並使用這兩個檔案判斷完整的索引集。
再次看看下方的範例查詢:
select from Person where lastName = 'Smith'
&& height < 72
order by height desc
這個查詢所需的索引設定會出現在 datastore-indexes.xml,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
xmlns="http://appengine.google.com/ns/datastore-indexes/1.0"
autoGenerate="true">
<datastore-index kind="Person" ancestor="false">
<property name="lastName" direction="asc" />
<property name="height" direction="desc" />
</datastore-index>
</datastore-indexes>
如果 datastore-indexes.xml 中的 <datastore-indexes> 元素具備 autoGenerate="true" 屬性 (attribute) (如上所述),或者如果應用程式沒有 datastore-indexes.xml 檔案,則自動索引設定將啟用。在自動索引設定已啟用的情況下,如果應用程式在開發伺服器上執行這個查詢,卻沒有為這個索引建立任何設定,則伺服器會將這個索引設定新增至 datastore-indexes-auto.xml 檔案。
如需 datastore-indexes.xml 和 datastore-indexes-auto.xml 的詳細資訊,請參閱「Java 資料存放區索引設定」。
實體金鑰可以是查詢篩選器或排序順序的主體。在 JDO 中,您可以透過物件的主要金鑰欄位參考查詢的實體金鑰。資料存放區會考慮這類查詢的完整金鑰值,包括實體的父系路徑、種類,以及應用程式指派的金鑰名稱字串或系統指派的數字 ID。
實體金鑰在系統的所有實體中是唯一的,因此金鑰查詢可以輕鬆地批次擷取指定種類的實體,例如批次傾出資料存放區的內容。不同於 JDOQL 範圍,這個方法不會因為實體數目的多寡而影響效率。
金鑰會先依照父系路徑排序,接著依照種類,再依照金鑰名稱或 ID 排序。種類和金鑰名稱為字串,並使用位元組值排序。ID 為整數,並使用數字大小排序。如果實體的父系和種類相同,卻混合使用字串名稱和數字 ID 的金鑰,則擁有數字 ID 的實體會排在擁有字串名稱的實體後面。父系路徑元素的比較方式也很類似:先依照種類 (字串) 排序,再依照金鑰名稱 (字串) 或 ID (數字) 排序。
使用具有索引之金鑰的查詢,跟使用屬性的查詢十分相像,唯一的差異在於:使用等式篩選器篩選金鑰、且擁有其他篩選器的查詢,必須使用應用程式的索引設定檔所定義的自訂索引。與所有的查詢一樣,當需要自訂索引的查詢遭到測試時,開發網頁伺服器會在這個檔案中建立適當的設定。
索引查詢機制本身具有一些執行查詢的限制。
屬性的查詢篩選器條件或排序順序,隱含一個條件,那就是實體的該屬性要有值。
如果其他實體具有相同種類的屬性,資料存放區實體的該屬性便不需要有值。屬性的篩選器只能比對該屬性有值的實體。在篩選器或排序順序中使用的實體屬性若沒有值,為查詢建置的索引會省略這樣的實體。
實體如果缺乏指定屬性,便不可能進行查詢。您可以使用 null 預設值建立固定的 (模型化的) 屬性,再為屬性值為 null 的實體建立篩選器。
在查詢的所有篩選器中,只能在一個屬性上使用不等式篩選器 (<、<=、>=、>)。
例如,這個查詢是成立的:
select from Person where birthYear >= minBirthYearParam
&& birthYear <= maxBirthYearParam
但是,這個查詢卻「不成立」,因為它在兩個不同的屬性上使用不等式篩選器:
select from Person where birthYear >= minBirthYearParam
&& height >= minHeightParam // ERROR
在同一個查詢中,篩選器可以為不同屬性合併等式 (==) 比較,包括使用一或多個不等式條件的屬性。這是允許的:
select from Person where lastName == lastNameParam
&& city == cityParam
&& birthYear >= minBirthYearParam
查詢的所有結果在索引表格中必須相鄰,否則查詢機制就必須掃描整個表格才能獲得結果。如果希望所有結果在表格中都維持連續,單一索引表格就不能代表多個屬性上的多個不等式篩選器。
如果某查詢同時具有含不等式比較的篩選器以及一或多個排序順序,則查詢必須包含該不等式的屬性排序順序,而該排序順序必須出現在其他屬性的排序順序之前。
這個查詢是「無效」的,因為它使用不等式篩選器,卻沒有依照篩選的屬性排序:
select from Person where birthYear >= minBirthYearParam
order by lastName // ERROR
同樣地,這個查詢也是無效的,因為在使用其他屬性排序之前,它沒有先依照篩選的屬性排序:
select from Person where birthYear > minBirthYearParam
order by lastName, birthYear // ERROR
這個查詢是有效的:
select from Person where birthYear >= minBirthYearParam
order by birthYear, lastName
為取得符合不等式篩選器的所有結果,查詢會掃描索引表格,找到第一個符合的列,再傳回所有連續的結果,直到找到不符合的列為止。如要使用連續列做為完整的結果集,這些列必須在其他排序順序之前,先依照不等式篩選器排序。
因為多重值屬性的索引方式不同,這些屬性的排序順序也十分特殊:
此排序順序會有不尋常的結果,在遞增和遞減排序中,[1, 9] 都會排在 [4, 5, 6, 7] 的前面。
請注意,如果查詢同時擁有等式篩選器和多重值屬性之排序順序,則排序順序會被忽略。對單一值屬性而言,這是一種簡單的最佳化方式。每個結果都會有相同的值,因此結果不需要進一步排序。
不過,多重值屬性可能擁有其他的值。因為排序順序被忽略,所以查詢結果傳回的順序可能會與套用排序順序的結果不同。(還原丟棄的排序順序的代價很高,而且需要額外的索引,不過這種情況很少發生,因此查詢規劃人員決定不處理。)
如上所述,每個實體的每個屬性 (不含 Text 或 Blob 值者) 都會被新增到至少一個索引表格,包括預設提供的簡單索引,以及應用程式參考屬性的 datastore-indexes.xml 檔案所描述的任何索引。對於每個屬性都有單一個值的實體,「應用服務引擎」的簡單索引會儲存其屬性值一次,而每當自訂索引參考該屬性時,會再儲存一次。每次屬性值變更時,所有索引項目都必須更新,因此如果參考同一個屬性的索引增加,則該屬性的更新時間也會拉長。
為了防止實體的更新時間太長,資料存放區會限制單一實體可以擁有的索引項目數量。此限制訂得很寬鬆,因此大部分應用程式都不會有這個問題。然而,仍有部分情況可能會達到上限。例如實體具有非常多的單一值屬性,就可能超出索引項目限制。
擁有多個值的屬性會將每個值以個別項目儲存在索引中。因此,即使實體只有一個屬性,只要該屬性擁有多個值,仍可能會超過索引項目限制。
自訂索引如果參考多個多重值屬性,則只要幾個值就可能變得非常龐大。如要完整記錄這類屬性,索引表格必須為每個屬性的「每個屬性值排列」提供一列資料列。
例如,下列索引 (以 datastore-indexes.xml 語法描述) 包括 x 和 y 屬性供種類 MyModel 的實體使用:
<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes>
<datastore-index kind="MyModel">
<property name="x" direction="asc" />
<property name="y" direction="asc" />
</datastore-index>
</datastore-indexes>
下列程式碼建立的實體,具有 x 屬性的兩個值以及 y 屬性的兩個值:
MyModel m = new MyModel();
m.setX(Arrays.asList("one", "two"));
m.setY(Arrays.asList("three", "four"));
pm.makePersistent(m);
如要正確呈現這些值,索引必須儲存 12 個屬性值:x 和 y 的內建索引分別使用 2 個,以及自訂索引中的 x 和 y 共 4 個排列分別使用 2 個。由於多重值屬性的值很多,因此索引可能需要為單一的實體儲存大量索引項目。您可以把參考多個多重值屬性的索引稱為「過量索引」,因為只要幾個屬性值,該索引就可能變得非常龐大。
如果 put() 導致索引項目的數量超過上限,則呼叫會失敗,並發生例外狀況。在您建立的新索引中,如果有部分索引項目都超出建立實體時的上限,就無法查詢該索引;而在「管理控制台」中,索引會顯示為「錯誤」狀態。
只要避免查詢需要使用清單屬性的自訂索引,就可以避免過量索引的發生。如上所述,這包括使用多個排序順序的查詢、混合使用等式和不等式篩選器的查詢,以及使用上階篩選器的查詢。