在 JDO 中建立、取得和刪除資料

將 JDO 資料物件儲存至資料儲存庫的做法很簡單,只要呼叫 PersistenceManager 例項的 makePersistent() 方法即可。App Engine JDO 實作使用物件的主要金鑰欄位來追蹤與資料物件相對應的資料儲存庫實體,並且可自動為新物件產生金鑰。您可以使用金鑰快速擷取實體,也可以用已知值來建構金鑰 (如帳戶 ID)。

將物件設為持續性物件

如要在資料儲存庫中儲存簡單的資料物件,請呼叫 PersistenceManager 的 makePersistent() 方法,並傳遞該例項。

PersistenceManager pm = PMF.get().getPersistenceManager();

Employee e = new Employee("Alfred", "Smith", new Date());

try {
    pm.makePersistent(e);
} finally {
    pm.close();
}

makePersistent() 的呼叫為同步,必須等到物件儲存且索引更新後才會傳回。

如要在 JDO 中儲存多個物件,請使用物件集合呼叫 makePersistentAll(...) 方法。這個方法會使用單一低階批次儲存作業,比一系列個別 makePersistent(...) 叫用更有效率。

附註:如有任一資料物件的持續性欄位參照的是其他持續性資料物件,而且這些物件中有任何一個物件未經儲存或經過載入後遭到變更,那麼這些參照的物件也會儲存至資料儲存庫。請參閱「關係」。

金鑰

App Engine 中的每個實體都有專屬的獨特金鑰。完整的金鑰含有以下資訊:應用程式 ID、種類和實體 ID。(金鑰也包含實體群組的相關資訊;詳情請參閱交易一文)。

物件的金鑰儲存在執行個體的欄位中,您可以使用 @PrimaryKey 註解來識別主鍵欄位。

金鑰的 ID 部分可由應用程式在建立物件時提供字串形式的 ID,也可由資料儲存庫自動產生數字 ID。資料儲存庫中所有實體的完整金鑰皆不得重複,換句話說,所有種類相同且實體群組父項 (如果有的話) 相同的物件都必須具有獨特的 ID。您可使用欄位類型和註解對金鑰指定您要的行為。

如果類別在關係中做為「子項」類別使用,金鑰欄位必須屬於能夠表示實體群組父項的類型:金鑰執行個體,或編碼為字串的金鑰值。如要進一步瞭解實體群組,請參閱「交易」一文;如要進一步瞭解關係,請參閱「關係」一文。

提示:如果應用程式建立了新物件且為此物件指定的字串 ID 與同一種類 (且實體群組父項也相同) 的其他物件的字串 ID 相同,則儲存這個新物件會覆寫資料儲存庫中的既有物件。如要在建立新物件之前先偵測某個字串 ID 是否已由其他物件使用,您可試著透過交易取得具有特定 ID 的實體,如果交易結果表示沒有具有該 ID 的實體,您就可以使用這個 ID 建立新物件。請參閱交易

主要金鑰欄位有以下四種類型:

長整數 (java.lang.Long),資料儲存庫自動產生的實體 ID。如果物件沒有實體群組父項,且物件 ID 必須由資料儲存庫自動產生,請使用這種類型。執行個體的長金鑰欄位是在您儲存此執行個體時產生的。

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
未編碼的字串

字串 (java.lang.String),應用程式在建立物件時提供的實體 ID (「金鑰名稱」)。如果物件沒有實體群組父項,且物件 ID 必須由應用程式提供,請使用這種類型。應用程式會在儲存之前,將此欄位設為偏好的 ID。

import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    private String name;

鍵例項 (com.google.appengine.api.datastore.Key)。鍵值包含實體群組父項的鍵 (如有),以及應用程式指派的字串 ID 或系統產生的數字 ID。如要使用應用程式指派的字串 ID 來建立物件,您可以使用該 ID 建立金鑰值,再將欄位設為此值。如要使用系統指派的數字 ID 來建立物件,您可以將金鑰欄位設為空值。(如要進一步瞭解如何使用實體群組父項,請參閱「交易」一文)。

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    public void setKey(Key key) {
        this.key = key;
    }

應用程式可使用 KeyFactory 類別建立金鑰執行個體:

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

// ...
        Key key = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
        Employee e = new Employee();
        e.setKey(key);
        pm.makePersistent(e);
編碼字串形式的金鑰

與金鑰類型相似,但其值為編碼字串形式的金鑰。編碼字串金鑰可讓您透過可攜方式編寫應用程式,同時仍可運用 App Engine 資料儲存庫實體群組的種種優點。

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

// ...
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;

應用程式可以在儲存之前,使用具備名稱的金鑰填入此值,或者將其設為空值。如果編碼金鑰欄位為空值,則儲存物件時,該欄位將填入系統產生的金鑰。

您可以使用 KeyFactory 方法 keyToString()stringToKey(),將金鑰例項轉換為編碼字串表示法,也可以將編碼字串表示法轉換為金鑰例項。

使用編碼金鑰字串時,您可透過額外欄位提供物件字串或數字 ID 的存取權:

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
    private String encodedKey;

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
    private String keyName;

    // OR:

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.pk-id", value="true")
    private Long keyId;

在儲存物件之前,您可以將 "gae.pk-name" 欄位設為鍵名。儲存物件之後,編碼金鑰欄位即填入含有金鑰名稱的完整金鑰,其類型必須為 String

物件儲存時會填入 "gae.pk-id" 欄位,且無法修改。其類型必須是 Long。

建立具有所產生金鑰的新物件 (使用 valueStrategy = IdGeneratorStrategy.IDENTITY 的金鑰欄位) 時,其金鑰值一開始為 null。將物件寫入資料儲存庫時,該金鑰欄位即填入一個值。如果您使用的是交易,那麼會在交易修訂後寫入物件。否則,如果系統正在建立物件,則會在呼叫 makePersistent() 方法時寫入物件;如果系統正在更新物件,則會在呼叫 PersistenceManager 例項的 close() 方法時寫入物件。

如要進一步瞭解如何設定金鑰,請參閱實體、屬性和金鑰一文。

透過金鑰取得物件

如要根據金鑰擷取物件,請使用 PersistenceManager 的 getObjectById() 方法。這個方法採用的是物件類別和金鑰:

        Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "Alfred.Smith@example.com");
        Employee e = pm.getObjectById(Employee.class, k);

如果類別使用索引鍵欄位,且該欄位為未編碼的字串 ID (String) 或數字 ID (Long),getObjectByID() 可以將簡單值設為索引鍵參數:

        Employee e = pm.getObjectById(Employee.class, "Alfred.Smith@example.com");

金鑰引數的類型可以是任一支援的金鑰欄位類型 (字串 ID、數字 ID、金鑰值、編碼金鑰字串),也可以是類別中金鑰欄位類型以外的其他類型。App Engine 必須能夠從類別名稱和提供的值衍生出完整金鑰;字串 ID 和數字 ID 兩者互斥,因此,使用數字 ID 的呼叫永遠不會傳回含有字串 ID 的實體。如果使用的是金鑰值或編碼金鑰字串,其金鑰必須參照由類別代表其種類的實體。

更新物件

透過 JDO 更新物件的其中一種方式是擷取物件,然後在傳回物件的 PersistenceManager 尚未關閉前修改該物件。PersistenceManager 關閉之後,變更仍會存續。例如:

public void updateEmployeeTitle(User user, String newTitle) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    try {
        Employee e = pm.getObjectById(Employee.class, user.getEmail());
        if (titleChangeIsAuthorized(e, newTitle) {
            e.setTitle(newTitle);
        } else {
            throw new UnauthorizedTitleChangeException(e, newTitle);
        }
    } finally {
        pm.close();
    }
}

由於 Employee 例項是由 PersistenceManager 傳回,因此 PersistenceManager 會知道對 Employee 上的 Persistent 欄位所做的任何修改,並在 PersistenceManager 關閉時自動更新資料儲存庫。PersistenceManager 之所以知道這些,是因為 Employee 執行個體已「附加」至 PersistenceManager。

您可以將類別宣告為「可分離」的,藉此在 PersistenceManager 關閉後修改物件。方法是在 @PersistenceCapable 註解中加入 detachable 屬性:

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(detachable="true")
public class Employee {
    // ...
}

現在,即使載入 Employee 物件的 PersistenceManager 已關閉,您仍然可以讀取和寫入該物件的欄位。以下範例說明卸離物件的實用性:

public Employee getEmployee(User user) {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Employee employee, detached = null;
    try {
        employee = pm.getObjectById(Employee.class,
            "Alfred.Smith@example.com");

        // If you're using transactions, you can call
        // pm.setDetachAllOnCommit(true) before committing to automatically
        // detach all objects without calls to detachCopy or detachCopyAll.
        detached = pm.detachCopy(employee);
    } finally {
        pm.close();
    }
    return detached;
}

public void updateEmployeeTitle(Employee e, String newTitle) {
    if (titleChangeIsAuthorized(e, newTitle) {
        e.setTitle(newTitle);
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(e);
        } finally {
            pm.close();
        }
    } else {
        throw new UnauthorizedTitleChangeException(e, newTitle);
    }
}

如果使用卸離物件,即不需要建立資料移轉物件。如要進一步瞭解如何使用已分離的物件,請參閱 DataNucleus 說明文件

刪除物件

如要從資料儲存庫中刪除物件,請使用物件呼叫 PersistenceManager 的 deletePersistent() 方法:

pm.deletePersistent(e);

如要在 JDO 中刪除多個物件,請使用物件集合呼叫 deletePersistentAll(...) 方法。這個方法會使用單一低階批次刪除作業,比起一連串個別 deletePersistent(...) 叫用作業,效率更高。

如果物件欄位含有同屬持續性的子項物件,則這些子項物件也會遭到刪除。詳情請參閱「關聯性」。

如要刪除符合查詢的所有物件,您可以使用 JDOQL 的「依查詢刪除」功能。詳情請參閱依查詢刪除實體一文。