Transazioni

Datastore supporta le transazioni. Una transazione è un'operazione insieme di operazioni atomiche, ovvero tutte le operazioni della transazione o non avviene nessuno di questi. Un'applicazione può eseguire più operazioni i calcoli in un'unica transazione.

Utilizzo delle transazioni

Una transazione è un insieme di operazioni Datastore su una o più le entità. Ogni transazione è garantita come atomica, il che significa che le transazioni non vengono mai applicate parzialmente. Vengono applicate tutte le operazioni nella transazione o nessuna di esse. Le transazioni hanno un durata massima di 60 secondi con una scadenza di inattività di 10 secondi dopo 30 secondi secondi.

Un'operazione potrebbe non riuscire quando:

  • Sono state tentate troppe modifiche simultanee nello stesso gruppo di entità.
  • La transazione supera un limite di risorse.
  • Datastore rileva un errore interno.

In tutti questi casi, l'API Datastore genera un'eccezione.

Le transazioni sono una funzionalità facoltativa di Datastore; non sei necessarie per utilizzare le transazioni per eseguire operazioni di Datastore.

Ecco un esempio di aggiornamento del campo denominato vacationDays in un'entità di tipo Employee denominato Joe:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Transaction txn = datastore.beginTransaction();
try {
  Key employeeKey = KeyFactory.createKey("Employee", "Joe");
  Entity employee = datastore.get(employeeKey);
  employee.setProperty("vacationDays", 10);

  datastore.put(txn, employee);

  txn.commit();
} finally {
  if (txn.isActive()) {
    txn.rollback();
  }
}

Tieni presente che per mantenere gli esempi più concisi a volte omettiamo la parte Blocco finally che esegue un rollback se la transazione è ancora attiva. Nel di produzione è importante garantire che ogni transazione di cui è stato eseguito il commit esplicito o il rollback.

Gruppi di entità

Ogni entità appartiene a un gruppo di entità, ovvero a un insieme di una o più entità possono essere manipolati in un'unica transazione. Le relazioni tra gruppi di entità in App Engine per archiviare diverse entità nella stessa parte in ogni rete. Una transazione configura le operazioni di Datastore per gruppo di entità e tutte le operazioni vengono applicate come gruppo o non vengono applicate affatto la transazione non va a buon fine.

Quando l'applicazione crea un'entità, può assegnare un'altra entità come parent della nuova entità. L'assegnazione di un'entità padre a una nuova entità comporta l'applicazione nello stesso gruppo di entità dell'entità padre.

Un'entità senza un'entità padre è un'entità principale. Un'entità padre di anche un'altra entità può avere un elemento padre. Una catena di entità padre di un'entità fino alla radice c'è il percorso dell'entità, mentre i membri del percorso sono i antenati dell'entità. L'entità principale di un'entità viene definita al momento della sua creazione e non può essere modificata in un secondo momento.

Ogni entità con una determinata entità base come predecessore si trova nella stessa entità gruppo. Tutte le entità di un gruppo vengono archiviate nello stesso nodo Datastore. Una singola transazione può modificare più entità in un singolo gruppo oppure aggiungi nuove entità al gruppo rendendo esistente l'elemento principale della nuova entità nel gruppo. Il seguente snippet di codice illustra le transazioni vari tipi di entità:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Entity person = new Entity("Person", "tom");
datastore.put(person);

// Transactions on root entities
Transaction txn = datastore.beginTransaction();

Entity tom = datastore.get(person.getKey());
tom.setProperty("age", 40);
datastore.put(txn, tom);
txn.commit();

// Transactions on child entities
txn = datastore.beginTransaction();
tom = datastore.get(person.getKey());
Entity photo = new Entity("Photo", tom.getKey());

// Create a Photo that is a child of the Person entity named "tom"
photo.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg");
datastore.put(txn, photo);
txn.commit();

// Transactions on entities in different entity groups
txn = datastore.beginTransaction();
tom = datastore.get(person.getKey());
Entity photoNotAChild = new Entity("Photo");
photoNotAChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg");
datastore.put(txn, photoNotAChild);

// Throws IllegalArgumentException because the Person entity
// and the Photo entity belong to different entity groups.
txn.commit();

Creazione di un'entità in un gruppo di entità specifico

Quando l'applicazione crea una nuova entità, puoi assegnarla a un'entità fornendo la chiave di un'altra entità. L'esempio seguente crea la chiave di un'entità MessageBoard, quindi la utilizza per creare e mantenere una entità Message che si trova nello stesso gruppo di entità di MessageBoard:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

String messageTitle = "Some Title";
String messageText = "Some message.";
Date postDate = new Date();

Key messageBoardKey = KeyFactory.createKey("MessageBoard", boardName);

Entity message = new Entity("Message", messageBoardKey);
message.setProperty("message_title", messageTitle);
message.setProperty("message_text", messageText);
message.setProperty("post_date", postDate);

Transaction txn = datastore.beginTransaction();
datastore.put(txn, message);

txn.commit();

Utilizzo delle transazioni tra gruppi

Le transazioni tra gruppi (chiamate anche transazioni XG) operano su più gruppi gruppi di entità, che si comportano come transazioni a un singolo gruppo descritte sopra, ad eccezione di che le transazioni tra gruppi non vadano a buon fine se il codice tenta di aggiornare entità da più gruppi di entità.

L'utilizzo di una transazione tra gruppi è simile all'utilizzo di una transazione tra gruppi transazione, ma devi specificare che la transazione venga eseguire il raggruppamento incrociato quando inizi la transazione, utilizzando TransactionOptions:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
TransactionOptions options = TransactionOptions.Builder.withXG(true);
Transaction txn = datastore.beginTransaction(options);

Entity a = new Entity("A");
a.setProperty("a", 22);
datastore.put(txn, a);

Entity b = new Entity("B");
b.setProperty("b", 11);
datastore.put(txn, b);

txn.commit();

Cosa si può fare in una transazione

Datastore impone delle restrizioni su ciò che può essere fatto all'interno di una singola transazione.

Tutte le operazioni del datastore in una transazione devono essere eseguite su entità nello stesso gruppo di entità se la transazione è di un solo gruppo oppure su entità in un massimo di venticinque gruppi di entità se la transazione è tra gruppi. Questo include l'esecuzione di query sulle entità per predecessore, il recupero delle entità per chiave, l'aggiornamento entità e l'eliminazione di entità. Nota che ogni entità base appartiene a un un gruppo di entità separato, quindi una singola transazione non può creare o operare su più di entità base, a meno che non si tratti di una transazione tra gruppi.

Quando due o più transazioni tentano contemporaneamente di modificare entità in una o più gruppi di entità comuni, solo la prima transazione a cui viene eseguito il commit delle modifiche possono avere successo; tutti gli altri avranno esito negativo al momento del commit. Grazie a questa progettazione, l'uso I gruppi di entità limitano il numero di scritture simultanee che puoi eseguire su qualsiasi entità in gruppi. Quando inizia una transazione, Datastore utilizza controllo ottimistico della contemporaneità controllando l'ora dell'ultimo aggiornamento dei gruppi di entità utilizzati nella transazione. Dopo il commit di una transazione per i gruppi di entità, Datastore nuovamente controlla l'ora dell'ultimo aggiornamento dei gruppi di entità utilizzati nella transazione. Se è cambiato rispetto al controllo iniziale viene generata un'eccezione di Google.

Un'app può eseguire una query durante una transazione, ma solo se include un filtro di antenato. Un'app può anche ottenere entità Datastore per chiave durante una transazione. Puoi preparare le chiavi prima della transazione oppure puoi creare chiavi all'interno della transazione con i relativi nomi o ID.

Isolamento e coerenza

Al di fuori delle transazioni, il livello di isolamento di Datastore è quello più vicino per leggere il commit. All'interno delle transazioni viene applicato l'isolamento serializzabile. Ciò significa che un'altra transazione non può contemporaneamente modificare i dati lette o modificate da questa transazione.

In una transazione, tutte le letture riflettono lo stato attuale e coerente di Datastore al momento dell'inizio della transazione. Le query e i get all'interno di una transazione hanno la garanzia di vedere un singolo snapshot coerente di Datastore dall'inizio della transazione. Entità e le righe di indice nel gruppo di entità della transazione vengono aggiornate in modo che le query restituiscono l'insieme completo e corretto di entità di risultati, senza i falsi positivi o falsi negativi che possono verificarsi nelle query al di fuori delle transazioni.

Questa visualizzazione istantanea coerente si estende anche alle letture dopo le scritture all'interno transazioni. A differenza della maggior parte dei database, esegue query e ottiene all'interno di un La transazione Datastore non vede il i risultati delle precedenti scritture all'interno della transazione. Nello specifico, se un'entità viene modificata o eliminata all'interno di una transazione, una query o un get restituisce la versione originale dell'entità dall'inizio della transazione o nessuna entità se l'entità non esisteva.

Utilizzi per le transazioni

Questo esempio mostra un utilizzo delle transazioni: l'aggiornamento di un'entità con un nuovo rispetto al suo valore attuale. Poiché L'API Datastore non riprova a eseguire le transazioni, possiamo aggiungere una logica per ritentare la transazione nel caso in cui un'altra richiesta aggiorni la stessa MessageBoard o uno qualsiasi dei suoi Messages contemporaneamente.

int retries = 3;
while (true) {
  Transaction txn = datastore.beginTransaction();
  try {
    Key boardKey = KeyFactory.createKey("MessageBoard", boardName);
    Entity messageBoard = datastore.get(boardKey);

    long count = (Long) messageBoard.getProperty("count");
    ++count;
    messageBoard.setProperty("count", count);
    datastore.put(txn, messageBoard);

    txn.commit();
    break;
  } catch (ConcurrentModificationException e) {
    if (retries == 0) {
      throw e;
    }
    // Allow retry to occur
    --retries;
  } finally {
    if (txn.isActive()) {
      txn.rollback();
    }
  }
}

Questa operazione richiede una transazione perché il valore potrebbe essere aggiornato da un altro utente dopo che questo codice recupera l'oggetto, ma prima di salvare l'oggetto modificato. Senza una transazione, la richiesta dell'utente utilizza il valore count precedente al valore l'aggiornamento di un altro utente e il salvataggio sovrascrive il nuovo valore. Con un transazione, all'applicazione viene comunicato l'aggiornamento dell'altro utente. Se l'entità viene aggiornata durante la transazione, transazione non va a buon fine e restituisce un valore ConcurrentModificationException. L'applicazione può ripetere la transazione per utilizzare i nuovi dati.

Un altro utilizzo comune delle transazioni è recuperare un'entità con una chiave denominata o crearla se non esiste ancora:

Transaction txn = datastore.beginTransaction();
Entity messageBoard;
Key boardKey;
try {
  boardKey = KeyFactory.createKey("MessageBoard", boardName);
  messageBoard = datastore.get(boardKey);
} catch (EntityNotFoundException e) {
  messageBoard = new Entity("MessageBoard", boardName);
  messageBoard.setProperty("count", 0L);
  boardKey = datastore.put(txn, messageBoard);
}
txn.commit();

Come prima, è necessaria una transazione per gestire il caso in cui un altro utente stia tentando di creare o aggiornare un'entità con lo stesso ID stringa. Senza un transazione, se l'entità non esiste e due utenti tentano di crearla, il secondo sostituisce la prima senza sapere che è successo. Con una transazione, il secondo tentativo non va a buon fine a livello atomico. Se ha senso farlo, l'applicazione può riprovare a recuperare l'entità aggiornalo.

Quando una transazione non va a buon fine, puoi chiedere all'app di riprovare la transazione finché non riesce oppure puoi consentire agli utenti di gestire l'errore propagandolo il livello dell'interfaccia utente dell'app. Non è necessario creare un loop di ripetizione ogni transazione.

Infine, puoi utilizzare una transazione per leggere uno snapshot coerente Datastore. Questo può essere utile quando sono necessarie più letture per visualizzare una pagina o esportare dati che devono essere coerenti. Questi tipi di transazioni sono spesso chiamate transazioni di sola lettura, poiché non eseguono scritture. Le transazioni di sola lettura su un singolo gruppo non vanno mai a buon fine a causa di in modo da non dover implementare nuovi tentativi in caso di errore. Tuttavia, le transazioni tra gruppi possono non riuscire a causa di modifiche simultanee, pertanto devono essere eseguite delle ripetizioni. Il commit e il rollback di una transazione di sola lettura entrambi autonomi.

DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

// Display information about a message board and its first 10 messages.
Key boardKey = KeyFactory.createKey("MessageBoard", boardName);

Transaction txn = datastore.beginTransaction();

Entity messageBoard = datastore.get(boardKey);
long count = (Long) messageBoard.getProperty("count");

Query q = new Query("Message", boardKey);

// This is an ancestor query.
PreparedQuery pq = datastore.prepare(txn, q);
List<Entity> messages = pq.asList(FetchOptions.Builder.withLimit(10));

txn.commit();

Accodamento delle attività transazionali

Puoi accodare un'attività come parte di una transazione Datastore, ad esempio che l'attività è in coda (e la garanzia sarà accodata) solo se la transazione eseguito correttamente il commit. Se il commit della transazione viene eseguito, l'attività viene saranno accodati. Una volta accodata, non è garantito che l'attività venga eseguita immediatamente e tutte le operazioni eseguite all'interno dell'attività vengono eseguite indipendentemente la transazione originale. L'attività riprova finché non viene completata correttamente. Vale per su qualsiasi attività accodata nel contesto di una transazione.

Le attività transazionali sono utili perché ti consentono azioni non Datastore in un Datastore transazione (ad esempio, l'invio di un'email per confermare un acquisto). Puoi anche legare Azioni Datastore sulla transazione, come il commit modifiche a gruppi di entità aggiuntivi al di fuori della transazione se e solo se la transazione ha esito positivo.

Un'applicazione non può inserire più di cinque attività transazionali in code di attività durante una una singola transazione. Le attività transazionali non devono avere nomi specificati dall'utente.

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Queue queue = QueueFactory.getDefaultQueue();
Transaction txn = datastore.beginTransaction();
// ...

queue.add(txn, TaskOptions.Builder.withUrl("/path/to/handler"));

// ...

txn.commit();