My favorites | 中文(繁體) | Sign in
英文版或許有比此中譯版新的內容

交易

「應用服務引擎」資料存放區支援「交易」功能。交易是一種 (或一組) 全部成功或全部失敗的操作。應用程式可以在單次交易中執行多項操作與計算。

使用交易

交易是一個資料存放區操作或一組資料存放區操作,這是全部成功或全部失敗的一種操作。如交易成功,資料存放區將套用所有的預期效果。如交易失敗,則不會套用任何效果。

每個資料存放區的寫入操作都是單一性的。因此操作不是嘗試建立、更新或刪除實體,就是完全不做嘗試。操作可能會因為爭用資源的頻率太高 (太多使用者同時嘗試修改實體) 而失敗。操作也可能會因為應用程式達到配額上限,或是資料存放區發生內容錯誤而失敗。在這些情況下,操作的效果將無法套用,且 Datastore API (資料存放區 API) 將引發例外狀況。

下列範例使用 JDO Transaction API (JDO 交易 API),為 ClubMembers 物件 (未顯示類別) 的 counter 欄位增量:

import javax.jdo.Transaction;

import ClubMembers;   // not shown

// ...
        // PersistenceManager pm = ...;

        Transaction tx = pm.currentTransaction();

        try {
            tx.begin();
    
            ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
            members.incrementCounterBy(1);
            pm.makePersistent(members);
    
            tx.commit();
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

實體群組

每個實體都隸屬於一個「實體群組」,也就是可以在單一交易中操縱的一或多個實體。實體群組的關聯性會告訴應用服務引擎,將幾個實體都儲存在分散式網路中的相同部分。 交易為實體群組設定資料存放區操作,而所有操作會以群組方式來一起套用;若交易失敗,則完全不套用。

應用程式建立實體時,它可以將另一個實體指派為這個新實體的「父系」。指派父系給新實體,會將新實體放置於父系實體相同的實體群組。

沒有父系的實體是「根」實體。一個實體即使已是另一個實體的父系,也可以再有父系。從實體到根的父系實體鏈結,是該實體的「路徑」,而該路徑的成員是實體的「上階」。實體建立時,就會定義其父系,而且之後無法變更。

具有指定根實體做為上階的每個實體,都位於相同的實體群組。群組中的所有實體,會儲存在相同的資料存放區節點。單一交易可以修改單一群組中的多個實體,也可以將新實體新增至群組 (指定群組中的現有實體為新實體的父系)。

使用實體群組建立實體

JDO 介面的「應用服務引擎」實作使用實體群組代表一對一的從屬關聯性或一對多的從屬關聯性。如此一來,物件的變更與其子系物件的變更將可以發生在相同的交易中。請參閱「關聯性」。

如果是其他情況,您可以使用明確的實體群組父系來建立實體,方法是將物件的主要金鑰欄位設為完整金鑰 (包括父系的金鑰)。物件的主要金鑰欄位必須是 Key 實例或編碼金鑰字串 (而非簡單的 Long 或 String 金鑰名稱)。

當使用應用程式指派的字串 ID 時,您可以使用實體群組父系來建立實體,方法是將其金鑰欄位設為完整金鑰值 (包括父系的金鑰)。如要使用實體群組父系來建立金鑰值,請使用 KeyFactory.Builder 類別,如下所示:

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

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class AccountInfo {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

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

// ...
        KeyFactory.Builder keyBuilder = new KeyFactory.Builder(Customer.class.getSimpleName(), "custid985135");
        keyBuilder.addChild(AccountInfo.class.getSimpleName(), "acctidX142516");
        Key key = keyBuilder.getKey();

        AccountInfo acct = new AccountInfo();
        acct.setKey(key);
        pm.makePersistent(acct);

您可以使用一個欄位,從物件的金鑰個別存取實體群組父系金鑰,如下所示:

// ...
    @Persistent
    @Extension(vendorName="datanucleus", key="gae.parent-pk", value="true")
    private Key customerKey;

如要使用系統指派的數字 ID 和實體群組父系來建立物件,您必須使用實體群組父系金鑰欄位 (例如上述的 customerKey)。請將父系的金鑰指派給父系金鑰欄位,然後將物件的金鑰欄位設為空值。儲存物件時,資料存放區會在金鑰欄位填入完整金鑰,包括實體群組父系。

如果類別擁有實體群組父系欄位,您可以在父系欄位的查詢中使用等式篩選器。(不支援在父系金鑰上使用不等式篩選器)。

交易中可以完成什麼

資料存放區針對單一交易中可完成的事項有一些限制。

交易中的所有資料存放區的操作對象必須是相同實體群組中的實體。這些操作包括使用金鑰擷取實體、更新實體以及刪除實體。請注意,每個根實體均屬於個別的實體群組,因此單一交易不能建立或操作超過一個的根實體。

應用程式在交易時無法執行查詢。不過,應用程式在交易時可以使用金鑰擷取資料存放區實體,且所擷取的實體與交易的其他部分絕對一致。您可以在交易前預先準備金鑰,或在交易中使用金鑰名稱或 ID 建立金鑰。

在單一交易中,應用程式只能建立或更新實體一次。

JDO 會透過單一交易,在 tx.begin() 呼叫和 tx.commit() 呼叫之間執行所有動作。如果因為另一個程序正在使用所要求的實體群組而導致任何動作失敗,JDO 會擲回由 java.util.ConcurrentModificationException 所引起的 JDODataStoreException 或 JDOException。

在使用開放式並行處理的系統中,應用程式會在多次嘗試交易不成後才放棄。而 JDO 只會執行交易一次,因此應用程式必須重複交易 (如果想要的話)。例如:

        for (int i = 0; i < NUM_RETRIES; i++) {
            pm.currentTransaction().begin();

            ClubMembers members = pm.getObjectById(ClubMembers.class, "k12345");
            members.incrementCounterBy(1);

            try {
                pm.currentTransaction().commit();
                break;

            } catch (JDOCanRetryException ex) {
                if (i == (NUM_RETRIES - 1)) { 
                    throw ex;
                }
            }
        }

如果試圖在相同交易中更新超過一個的實體群組,將擲回 JDOFatalUserException。 請注意,沒有實體群組父系的物件皆存放在自己的實體群組中,因此您無法在單一交易中建立多個無父系實體。

如果試圖在單一交易中多次更新相同實體 (例如使用重複的 makePersistent() 呼叫),將擲回 JDOFatalUserException。因此,只能在交易中修改持續性物件,然後呼叫 commit() 套用變更。

交易的用途

這個範例將說明交易的用途之一:以新的屬性值 (與目前值相關的值) 更新實體。

        Key k = KeyFactory.createKey("Employee", "k12345");
        Employee e = pm.getObjectById(Employee.class, k);
        e.counter += 1;
        pm.makePersistent(e);

在程式碼擷取物件到儲存修改的物件的這段時間內,其他使用者可能會更新這個值,因此必須進行交易。 如果沒有交易,則使用者的要求將採用其他使用者更新之前的 counter 值,而儲存的動作也會覆寫更新的值。但是藉由交易,應用程式將取得其他使用者的更新。 如果實體在交易期間更新,則交易將因為例外狀況而失敗。應用程式可以重複交易,以使用最新資料。

另一個常見的交易用途是透過具名金鑰更新實體,或在實體不存在的狀況下建立實體:

        // PersistenceManager pm = ...;

        Transaction tx = pm.currentTransaction();

        String id = "jj_industrial";
        String companyName = "J.J. Industrial";
       
        try {
            tx.begin();

            Key k = KeyFactory.createKey("SalesAccount", id);
            SalesAccount account;
            try {
                account = pm.getObjectById(Employee.class, k);
            } catch (JDOObjectNotFoundException e) {
                account = new SalesAccount();
                account.setId(id);
            }

            account.setCompanyName(companyName);
            pm.makePersistent(account);

            tx.commit();

        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

如上所述,出現其他的使用者試圖以相同的字串 ID 建立或更新實體時,即必須進行交易。當兩位使用者嘗試建立目前不存在的實體時,如果沒有交易,則第二位使用者會在不知情的狀況下,覆寫掉第一位使用者所建立的實體。 反之,如果使用交易,則第二位使用者的嘗試會完全失敗,不過應用程式可以選擇重試,以擷取新的實體並加以更新。

提示:交易發生時,應儘快完成,以減少交易所使用的實體發生變更的機會。一旦發生變更,就需要重試交易。因為資料存放區操作需要一致不變的操作環境,因此請儘可能在先交易之外準備資料,然後再執行交易以執行資料存放區操作。應用程式必須為交易中使用的物件準備金鑰,再擷取交易中的實體。

停止交易和移植現有的 JDO 應用程式

我們建議使用的 JDO 設定會將 datanucleus.appengine.autoCreateDatastoreTxns 屬性設為 true。這是「應用服務引擎」特有的屬性;這個屬性會指示 JDO 實作為資料存放區的交易與應用程式碼所管理的 JDO 交易建立關聯。如果您是從頭建立新的應用程式,您可以參考這個設定。不過,如果您已經擁有一個 JDO 應用程式,且想要在「應用服務引擎」上執行,建議您使用其他的永久性設定,將這個屬性值設為 false

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="false"/>
    </persistence-manager-factory>
</jdoconfig>

為瞭解這個設定的實用之處,請記住您只能夠在同一次交易的同一個實體群組的物件上操作。使用傳統資料庫建立的應用程式通常會採用全域交易,讓您可以更新單一交易中的任何記錄。然而,由於「應用服務引擎」資料存放區並不支援全域交易,因此原本採用全域交易的程式碼會引發例外狀況。 例外狀況發生時,您不需要逐一查看可能非常龐大的程式碼基底,再移除所有的交易管理程式碼;您只需要停止資料存放區交易即可。當然,停止交易並不能夠修正程式碼對多重記錄修改的單一性所採用的設定,但是這個動作可以協助應用程式正常運作,讓您能夠依據需求重構並擴充交易程式碼,而非一次重構所有程式碼。