Buka konsol

Transaksi dan penulisan batch

Cloud Firestore mendukung operasi yang menyeluruh untuk membaca dan menulis data. Dalam kumpulan operasi yang menyeluruh, seluruh operasi dapat berhasil atau tidak diterapkan sama sekali. Ada 2 jenis operasi yang menyeluruh di Cloud Firestore:

  • Transaksi: transaksi adalah sekumpulan operasi baca dan tulis pada satu atau beberapa dokumen.
  • Batch Operasi Tulis: batch operasi tulis adalah sekumpulan operasi tulis pada satu atau beberapa dokumen.

Setiap transaksi atau batch operasi tulis dapat menulis hingga maksimum 500 dokumen. Untuk batas tambahan yang terkait dengan operasi tulis, lihat Kuota dan Batas.

Mengupdate data dengan transaksi

Dengan menggunakan library klien Cloud Firestore, Anda dapat mengelompokkan beberapa operasi ke dalam 1 transaksi. Transaksi akan berguna saat Anda ingin mengupdate nilai kolom berdasarkan nilai saat ini, atau nilai beberapa kolom lainnya.

Transaksi terdiri dari operasi get() yang diikuti oleh sejumlah operasi tulis seperti set(), update(), atau delete(). Untuk pengeditan serentak, Cloud Firestore akan menjalankan keseluruhan transaksi kembali. Misalnya, jika transaksi membaca dokumen dan dokumen tersebut diubah oleh klien lain, Cloud Firestore akan mencoba kembali transaksi tersebut. Fitur ini memastikan bahwa transaksi menjalankan data yang terkini dan konsisten.

Transaksi tidak pernah menerapkan penulisan secara parsial. Semua penulisan akan dijalankan pada akhir transaksi yang sukses.

Saat menggunakan transaksi, perhatikan bahwa:

  • Operasi baca harus dilakukan sebelum operasi tulis.
  • Fungsi yang memanggil transaksi (fungsi transaksi) dapat dijalankan lebih dari 1 kali jika pengeditan serentak memengaruhi dokumen yang dibaca transaksi.
  • Fungsi transaksi seharusnya tidak langsung mengubah status aplikasi.
  • Transaksi akan gagal saat klien sedang offline.

Contoh berikut menunjukkan cara membuat dan menjalankan transaksi:

Web
// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

// Uncomment to initialize the doc.
// sfDocRef.set({ population: 0 });

return db.runTransaction(function(transaction) {
    // This code may get re-run multiple times if there are conflicts.
    return transaction.get(sfDocRef).then(function(sfDoc) {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        // Add one person to the city population.
        // Note: this could be done without a transaction
        //       by updating the population using FieldValue.increment()
        var newPopulation = sfDoc.data().population + 1;
        transaction.update(sfDocRef, { population: newPopulation });
    });
}).then(function() {
    console.log("Transaction successfully committed!");
}).catch(function(error) {
    console.log("Transaction failed: ", error);
});
Swift
let sfReference = db.collection("cities").document("SF")

db.runTransaction({ (transaction, errorPointer) -> Any? in
    let sfDocument: DocumentSnapshot
    do {
        try sfDocument = transaction.getDocument(sfReference)
    } catch let fetchError as NSError {
        errorPointer?.pointee = fetchError
        return nil
    }

    guard let oldPopulation = sfDocument.data()?["population"] as? Int else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -1,
            userInfo: [
                NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)"
            ]
        )
        errorPointer?.pointee = error
        return nil
    }

    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference)
    return nil
}) { (object, error) in
    if let error = error {
        print("Transaction failed: \(error)")
    } else {
        print("Transaction successfully committed!")
    }
}
Objective-C
FIRDocumentReference *sfReference =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) {
  FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer];
  if (*errorPointer != nil) { return nil; }

  if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{
      NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot"
    }];
    return nil;
  }
  NSInteger oldPopulation = [sfDocument.data[@"population"] integerValue];

  // Note: this could be done without a transaction
  //       by updating the population using FieldValue.increment()
  [transaction updateData:@{ @"population": @(oldPopulation + 1) } forDocument:sfReference];

  return nil;
} completion:^(id result, NSError *error) {
  if (error != nil) {
    NSLog(@"Transaction failed: %@", error);
  } else {
    NSLog(@"Transaction successfully committed!");
  }
}];
  
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF");

db.runTransaction(new Transaction.Function<Void>() {
    @Override
    public Void apply(Transaction transaction) throws FirebaseFirestoreException {
        DocumentSnapshot snapshot = transaction.get(sfDocRef);

        // Note: this could be done without a transaction
        //       by updating the population using FieldValue.increment()
        double newPopulation = snapshot.getDouble("population") + 1;
        transaction.update(sfDocRef, "population", newPopulation);

        // Success
        return null;
    }
}).addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        Log.d(TAG, "Transaction success!");
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.w(TAG, "Transaction failure.", e);
    }
});
Kotlin
val sfDocRef = db.collection("cities").document("SF")

db.runTransaction { transaction ->
    val snapshot = transaction.get(sfDocRef)

    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    val newPopulation = snapshot.getDouble("population")!! + 1
    transaction.update(sfDocRef, "population", newPopulation)

    // Success
    null
}.addOnSuccessListener { Log.d(TAG, "Transaction success!") }
        .addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
// Initialize doc
final DocumentReference docRef = db.collection("cities").document("SF");
City city = new City("SF");
city.setCountry("USA");
city.setPopulation(860000L);
docRef.set(city).get();

// run an asynchronous transaction
ApiFuture<Void> transaction =
    db.runTransaction(
        new Transaction.Function<Void>() {
          @Override
          public Void updateCallback(Transaction transaction) throws Exception {
            // retrieve document and increment population field
            DocumentSnapshot snapshot = transaction.get(docRef).get();
            long oldPopulation = snapshot.getLong("population");
            transaction.update(docRef, "population", oldPopulation + 1);
            return null;
          }
        });
// block on transaction operation using transaction.get()
Python
transaction = db.transaction()
city_ref = db.collection(u'cities').document(u'SF')

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    transaction.update(city_ref, {
        u'population': snapshot.get(u'population') + 1
    })

update_in_transaction(transaction, city_ref)
Node.js
// Initialize document
let cityRef = db.collection('cities').doc('SF');
let setCity = cityRef.set({
  name: 'San Francisco',
  state: 'CA',
  country: 'USA',
  capital: false,
  population: 860000
});

let transaction = db.runTransaction(t => {
  return t.get(cityRef)
    .then(doc => {
      // Add one person to the city population.
      // Note: this could be done without a transaction
      //       by updating the population using FieldValue.increment()
      let newPopulation = doc.data().population + 1;
      t.update(cityRef, {population: newPopulation});
    });
}).then(result => {
  console.log('Transaction success!');
}).catch(err => {
  console.log('Transaction failure:', err);
});
Go
ref := client.Collection("cities").Doc("SF")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
	doc, err := tx.Get(ref) // tx.Get, NOT ref.Get!
	if err != nil {
		return err
	}
	pop, err := doc.DataAt("population")
	if err != nil {
		return err
	}
	return tx.Set(ref, map[string]interface{}{
		"population": pop.(int64) + 1,
	}, firestore.MergeAll)
})
if err != nil {
	// Handle any errors appropriately in this section.
	log.Printf("An error has occurred: %s", err)
}
PHP
$cityRef = $db->collection('cities')->document('SF');
$db->runTransaction(function (Transaction $transaction) use ($cityRef) {
    $snapshot = $transaction->snapshot($cityRef);
    $newPopulation = $snapshot['population'] + 1;
    $transaction->update($cityRef, [
        ['path' => 'population', 'value' => $newPopulation]
    ]);
});
C#
DocumentReference cityRef = db.Collection("cities").Document("SF");
await db.RunTransactionAsync(async transaction =>
{
    DocumentSnapshot snapshot = await transaction.GetSnapshotAsync(cityRef);
    long newPopulation = snapshot.GetValue<long>("Population") + 1;
    Dictionary<string, object> updates = new Dictionary<string, object>
    {
        { "Population", newPopulation}
    };
    transaction.Update(cityRef, updates);
});
Ruby
def run_simple_transaction project_id:
  # project_id = "Your Google Cloud Project ID"

  firestore = Google::Cloud::Firestore.new project_id: project_id

  city_ref = firestore.doc "cities/SF"

  firestore.transaction do |tx|
    new_population = tx.get(city_ref).data[:population] + 1
    puts "New population is #{new_population}."
    tx.update city_ref, population: new_population
  end
  puts "Ran a simple transaction to update the population field in the SF document in the cities collection."
end

Mengeluarkan informasi dari transaksi

Jangan ubah status aplikasi di dalam fungsi transaksi Anda. Jika hal tersebut dilakukan, akan muncul masalah serentak. Hal ini bisa terjadi karena fungsi transaksi dapat dijalankan beberapa kali, dan tidak dijamin bisa dijalankan di UI thread. Sebagai gantinya, keluarkan informasi yang Anda butuhkan dari fungsi transaksi. Contoh berikut dibuat berdasarkan contoh sebelumnya untuk menunjukkan cara mengeluarkan informasi dari transaksi:

Web
// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

db.runTransaction(function(transaction) {
    return transaction.get(sfDocRef).then(function(sfDoc) {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        var newPopulation = sfDoc.data().population + 1;
        if (newPopulation <= 1000000) {
            transaction.update(sfDocRef, { population: newPopulation });
            return newPopulation;
        } else {
            return Promise.reject("Sorry! Population is too big.");
        }
    });
}).then(function(newPopulation) {
    console.log("Population increased to ", newPopulation);
}).catch(function(err) {
    // This will be an "population is too big" error.
    console.error(err);
});
Swift
let sfReference = db.collection("cities").document("SF")

db.runTransaction({ (transaction, errorPointer) -> Any? in
    let sfDocument: DocumentSnapshot
    do {
        try sfDocument = transaction.getDocument(sfReference)
    } catch let fetchError as NSError {
        errorPointer?.pointee = fetchError
        return nil
    }

    guard let oldPopulation = sfDocument.data()?["population"] as? Int else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -1,
            userInfo: [
                NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)"
            ]
        )
        errorPointer?.pointee = error
        return nil
    }

    // Note: this could be done without a transaction
    //       by updating the population using FieldValue.increment()
    let newPopulation = oldPopulation + 1
    guard newPopulation <= 1000000 else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -2,
            userInfo: [NSLocalizedDescriptionKey: "Population \(newPopulation) too big"]
        )
        errorPointer?.pointee = error
        return nil
    }

    transaction.updateData(["population": newPopulation], forDocument: sfReference)
    return newPopulation
}) { (object, error) in
    if let error = error {
        print("Error updating population: \(error)")
    } else {
        print("Population increased to \(object!)")
    }
}
Objective-C
FIRDocumentReference *sfReference =
[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) {
  FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer];
  if (*errorPointer != nil) { return nil; }

  if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{
      NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot"
    }];
    return nil;
  }
  NSInteger population = [sfDocument.data[@"population"] integerValue];

  population++;
  if (population >= 1000000) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-2 userInfo:@{
      NSLocalizedDescriptionKey: @"Population too big"
    }];
    return @(population);
  }

  [transaction updateData:@{ @"population": @(population) } forDocument:sfReference];

  return nil;
} completion:^(id result, NSError *error) {
  if (error != nil) {
    NSLog(@"Transaction failed: %@", error);
  } else {
    NSLog(@"Population increased to %@", result);
  }
}];
  
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF");

db.runTransaction(new Transaction.Function<Double>() {
    @Override
    public Double apply(Transaction transaction) throws FirebaseFirestoreException {
        DocumentSnapshot snapshot = transaction.get(sfDocRef);
        double newPopulation = snapshot.getDouble("population") + 1;
        if (newPopulation <= 1000000) {
            transaction.update(sfDocRef, "population", newPopulation);
            return newPopulation;
        } else {
            throw new FirebaseFirestoreException("Population too high",
                    FirebaseFirestoreException.Code.ABORTED);
        }
    }
}).addOnSuccessListener(new OnSuccessListener<Double>() {
    @Override
    public void onSuccess(Double result) {
        Log.d(TAG, "Transaction success: " + result);
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.w(TAG, "Transaction failure.", e);
    }
});
Kotlin
val sfDocRef = db.collection("cities").document("SF")

db.runTransaction { transaction ->
    val snapshot = transaction.get(sfDocRef)
    val newPopulation = snapshot.getDouble("population")!! + 1
    if (newPopulation <= 1000000) {
        transaction.update(sfDocRef, "population", newPopulation)
        newPopulation
    } else {
        throw FirebaseFirestoreException("Population too high",
                FirebaseFirestoreException.Code.ABORTED)
    }
}.addOnSuccessListener { result ->
    Log.d(TAG, "Transaction success: $result")
}.addOnFailureListener { e ->
    Log.w(TAG, "Transaction failure.", e)
}
Java
final DocumentReference docRef = db.collection("cities").document("SF");
ApiFuture<String> transaction =
    db.runTransaction(
        new Transaction.Function<String>() {
          @Override
          public String updateCallback(Transaction transaction) throws Exception {
            DocumentSnapshot snapshot = transaction.get(docRef).get();
            Long newPopulation = snapshot.getLong("population") + 1;
            // conditionally update based on current population
            if (newPopulation <= 1000000L) {
              transaction.update(docRef, "population", newPopulation);
              return "Population increased to " + newPopulation;
            } else {
              throw new Exception("Sorry! Population is too big.");
            }
          }
        });
// Print information retrieved from transaction
System.out.println(transaction.get());
Python
transaction = db.transaction()
city_ref = db.collection(u'cities').document(u'SF')

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    new_population = snapshot.get(u'population') + 1

    if new_population < 1000000:
        transaction.update(city_ref, {
            u'population': new_population
        })
        return True
    else:
        return False

result = update_in_transaction(transaction, city_ref)
if result:
    print(u'Population updated')
else:
    print(u'Sorry! Population is too big.')
Node.js
let cityRef = db.collection('cities').doc('SF');
let transaction = db.runTransaction(t => {
  return t.get(cityRef)
    .then(doc => {
      let newPopulation = doc.data().population + 1;
      if (newPopulation <= 1000000) {
        t.update(cityRef, {population: newPopulation});
        return Promise.resolve('Population increased to ' + newPopulation);
      } else {
        return Promise.reject('Sorry! Population is too big.');
      }
    });
}).then(result => {
  console.log('Transaction success', result);
}).catch(err => {
  console.log('Transaction failure:', err);
});
Go
ref := client.Collection("cities").Doc("SF")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
	doc, err := tx.Get(ref)
	if err != nil {
		return err
	}
	pop, err := doc.DataAt("population")
	if err != nil {
		return err
	}
	newpop := pop.(int64) + 1
	if newpop <= 1000000 {
		return tx.Set(ref, map[string]interface{}{
			"population": pop.(int64) + 1,
		}, firestore.MergeAll)
	}
	return errors.New("population is too big")
})
if err != nil {
	// Handle any errors in an appropriate way, such as returning them.
	log.Printf("An error has occurred: %s", err)
}
PHP
$cityRef = $db->collection('cities')->document('SF');
$transactionResult = $db->runTransaction(function (Transaction $transaction) use ($cityRef) {
    $snapshot = $transaction->snapshot($cityRef);
    $newPopulation = $snapshot['population'] + 1;
    if ($newPopulation <= 1000000) {
        $transaction->update($cityRef, [
            ['path' => 'population', 'value' => $newPopulation]
        ]);
        return true;
    } else {
        return false;
    }
});

if ($transactionResult) {
    printf('Population updated successfully.' . PHP_EOL);
} else {
    printf('Sorry! Population is too big.' . PHP_EOL);
}
C#
DocumentReference cityRef = db.Collection("cities").Document("SF");
bool transactionResult = await db.RunTransactionAsync(async transaction =>
{
    DocumentSnapshot snapshot = await transaction.GetSnapshotAsync(cityRef);
    long newPopulation = snapshot.GetValue<long>("Population") + 1;
    if (newPopulation <= 1000000)
    {
        Dictionary<string, object> updates = new Dictionary<string, object>
        {
            { "Population", newPopulation}
        };
        transaction.Update(cityRef, updates);
        return true;
    }
    else
    {
        return false;
    }
});

if (transactionResult)
{
    Console.WriteLine("Population updated successfully.");
}
else
{
    Console.WriteLine("Sorry! Population is too big.");
}
Ruby
def return_info_transaction project_id:
  # project_id = "Your Google Cloud Project ID"

  firestore = Google::Cloud::Firestore.new project_id: project_id

  city_ref = firestore.doc "cities/SF"

  updated = firestore.transaction do |tx|
    new_population = tx.get(city_ref).data[:population] + 1
    if new_population < 1_000_000
      tx.update city_ref, population: new_population
    else
      false
    end
  end

  if updated
    puts "Population updated!"
  else
    puts "Sorry! Population is too big."
  end
end

Kegagalan transaksi

Transaksi bisa gagal karena alasan berikut:

  • Pada transaksi tersebut, operasi baca dilakukan setelah operasi tulis. Operasi baca harus dilakukan sebelum operasi tulis.
  • Transaksi tersebut membaca dokumen yang telah diubah di luar transaksi. Dalam hal ini, transaksi akan berjalan kembali secara otomatis. Transaksi tersebut dicoba kembali beberapa kali.
  • Transaksi melebihi ukuran permintaan maksimum 10 MiB.

    Ukuran transaksi tergantung pada ukuran dokumen dan entri indeks yang dimodifikasi oleh transaksi. Untuk operasi penghapusan, ini termasuk ukuran dokumen target dan ukuran entri indeks yang dihapus sebagai tanggapan terhadap operasi.

Transaksi yang gagal akan menampilkan error dan tidak menulis apa pun ke database. Anda tidak perlu menjalankan kembali transaksi, karena Cloud Firestore akan melakukannya secara otomatis.

Batch operasi tulis

Jika Anda tidak harus membaca dokumen apa pun dalam kumpulan operasi, Anda dapat menjalankan beberapa operasi tulis sebagai 1 batch yang berisi kombinasi operasi set(), update(), atau delete(). Batch operasi tulis akan selesai secara menyeluruh dan bisa diterapkan ke beberapa dokumen. Contoh berikut menunjukkan cara membuat dan menulis batch:

Web
// Get a new write batch
var batch = db.batch();

// Set the value of 'NYC'
var nycRef = db.collection("cities").doc("NYC");
batch.set(nycRef, {name: "New York City"});

// Update the population of 'SF'
var sfRef = db.collection("cities").doc("SF");
batch.update(sfRef, {"population": 1000000});

// Delete the city 'LA'
var laRef = db.collection("cities").doc("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().then(function () {
    // ...
});
Swift
// Get new write batch
let batch = db.batch()

// Set the value of 'NYC'
let nycRef = db.collection("cities").document("NYC")
batch.setData([:], forDocument: nycRef)

// Update the population of 'SF'
let sfRef = db.collection("cities").document("SF")
batch.updateData(["population": 1000000 ], forDocument: sfRef)

// Delete the city 'LA'
let laRef = db.collection("cities").document("LA")
batch.deleteDocument(laRef)

// Commit the batch
batch.commit() { err in
    if let err = err {
        print("Error writing batch \(err)")
    } else {
        print("Batch write succeeded.")
    }
}
Objective-C
// Get new write batch
FIRWriteBatch *batch = [self.db batch];

// Set the value of 'NYC'
FIRDocumentReference *nycRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"NYC"];
[batch setData:@{} forDocument:nycRef];

// Update the population of 'SF'
FIRDocumentReference *sfRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[batch updateData:@{ @"population": @1000000 } forDocument:sfRef];

// Delete the city 'LA'
FIRDocumentReference *laRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"LA"];
[batch deleteDocument:laRef];

// Commit the batch
[batch commitWithCompletion:^(NSError * _Nullable error) {
  if (error != nil) {
    NSLog(@"Error writing batch %@", error);
  } else {
    NSLog(@"Batch write succeeded.");
  }
}];
  
Java
// Get a new write batch
WriteBatch batch = db.batch();

// Set the value of 'NYC'
DocumentReference nycRef = db.collection("cities").document("NYC");
batch.set(nycRef, new City());

// Update the population of 'SF'
DocumentReference sfRef = db.collection("cities").document("SF");
batch.update(sfRef, "population", 1000000L);

// Delete the city 'LA'
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
    @Override
    public void onComplete(@NonNull Task<Void> task) {
        // ...
    }
});
Kotlin
// Get a new write batch
val batch = db.batch()

// Set the value of 'NYC'
val nycRef = db.collection("cities").document("NYC")
batch.set(nycRef, City())

// Update the population of 'SF'
val sfRef = db.collection("cities").document("SF")
batch.update(sfRef, "population", 1000000L)

// Delete the city 'LA'
val laRef = db.collection("cities").document("LA")
batch.delete(laRef)

// Commit the batch
batch.commit().addOnCompleteListener {
    // ...
}
Java
// Get a new write batch
WriteBatch batch = db.batch();

// Set the value of 'NYC'
DocumentReference nycRef = db.collection("cities").document("NYC");
batch.set(nycRef, new City());

// Update the population of 'SF'
DocumentReference sfRef = db.collection("cities").document("SF");
batch.update(sfRef, "population", 1000000L);

// Delete the city 'LA'
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(laRef);

// asynchronously commit the batch
ApiFuture<List<WriteResult>> future = batch.commit();
// ...
// future.get() blocks on batch commit operation
for (WriteResult result :future.get()) {
  System.out.println("Update time : " + result.getUpdateTime());
}
Python
batch = db.batch()

# Set the data for NYC
nyc_ref = db.collection(u'cities').document(u'NYC')
batch.set(nyc_ref, {u'name': u'New York City'})

# Update the population for SF
sf_ref = db.collection(u'cities').document(u'SF')
batch.update(sf_ref, {u'population': 1000000})

# Delete LA
la_ref = db.collection(u'cities').document(u'LA')
batch.delete(la_ref)

# Commit the batch
batch.commit()
Node.js
// Get a new write batch
let batch = db.batch();

// Set the value of 'NYC'
let nycRef = db.collection('cities').doc('NYC');
batch.set(nycRef, {name: 'New York City'});

// Update the population of 'SF'
let sfRef = db.collection('cities').doc('SF');
batch.update(sfRef, {population: 1000000});

// Delete the city 'LA'
let laRef = db.collection('cities').doc('LA');
batch.delete(laRef);

// Commit the batch
return batch.commit().then(function () {
  // ...
});
Go
// Get a new write batch.
batch := client.Batch()

// Set the value of "NYC".
nycRef := client.Collection("cities").Doc("NYC")
batch.Set(nycRef, map[string]interface{}{
	"name": "New York City",
})

// Update the population of "SF".
sfRef := client.Collection("cities").Doc("SF")
batch.Set(sfRef, map[string]interface{}{
	"population": 1000000,
}, firestore.MergeAll)

// Delete the city "LA".
laRef := client.Collection("cities").Doc("LA")
batch.Delete(laRef)

// Commit the batch.
_, err := batch.Commit(ctx)
if err != nil {
	// Handle any errors in an appropriate way, such as returning them.
	log.Printf("An error has occurred: %s", err)
}
PHP
$batch = $db->batch();

# Set the data for NYC
$nycRef = $db->collection('cities')->document('NYC');
$batch->set($nycRef, [
    'name' => 'New York City'
]);

# Update the population for SF
$sfRef = $db->collection('cities')->document('SF');
$batch->update($sfRef, [
    ['path' => 'population', 'value' => 1000000]
]);

# Delete LA
$laRef = $db->collection('cities')->document('LA');
$batch->delete($laRef);

# Commit the batch
$batch->commit();
C#
WriteBatch batch = db.StartBatch();

// Set the data for NYC
DocumentReference nycRef = db.Collection("cities").Document("NYC");
Dictionary<string, object> nycData = new Dictionary<string, object>
{
    { "name", "New York City" }
};
batch.Set(nycRef, nycData);

// Update the population for SF
DocumentReference sfRef = db.Collection("cities").Document("SF");
Dictionary<string, object> updates = new Dictionary<string, object>
{
    { "Population", 1000000}
};
batch.Update(sfRef, updates);

// Delete LA
DocumentReference laRef = db.Collection("cities").Document("LA");
batch.Delete(laRef);

// Commit the batch
await batch.CommitAsync();
Ruby
firestore.batch do |b|
  # Set the data for NYC
  b.set "cities/NYC", name: "New York City"

  # Update the population for SF
  b.update "cities/SF", population: 1_000_000

  # Delete LA
  b.delete "cities/LA"
end

Penulisan batch dapat berisi hingga 500 operasi. Setiap operasi dalam batch dihitung secara terpisah terhadap penggunaan Cloud Firestore Anda. Dalam operasi tulis, kolom mengubah setiap hitungan serverTimestamp, arrayUnion, dan increment sebagai operasi tambahan.

Seperti halnya transaksi, penulisan batch bersifat menyeluruh. Berbeda dengan transaksi, penulisan batch tidak perlu memastikan bahwa dokumen yang dibaca tidak dimodifikasi sehingga dapat mengurangi kegagalan. Penulisan batch tidak tunduk pada percobaan ulang atau kegagalan dari terlalu banyak percobaan ulang. Penulisan batch tetap dijalankan meskipun perangkat pengguna sedang offline.

Validasi data untuk operasi yang menyeluruh

Untuk library klien seluler/web, Anda dapat melakukan validasi data menggunakan Aturan Keamanan Cloud Firestore. Anda dapat memastikan bahwa dokumen yang terkait selalu diperbarui secara menyeluruh dan selalu menjadi bagian dari sebuah transaksi atau batch operasi tulis. Gunakan fungsi aturan keamanan getAfter() untuk mengakses dan memvalidasi status dokumen setelah serangkaian operasi selesai, tetapi sebelum Cloud Firestore mulai beroperasi.

Misalnya, anggaplah database untuk contoh cities juga berisi koleksi countries. Setiap dokumen country menggunakan kolom last_updated untuk melacak kapan terakhir kali kota yang terkait dengan negara itu diperbarui. Aturan keamanan berikut mensyaratkan bahwa pembaruan pada dokumen city juga harus memperbarui kolom last_updated negara yang terkait secara menyeluruh:

service cloud.firestore {
  match /databases/{database}/documents {
    // If you update a city doc, you must also
    // update the related country's last_updated field.
    match /cities/{city} {
      allow write: if request.auth.uid != null &&
        getAfter(
          /databases/$(database)/documents/countries/$(request.resource.data.country)
        ).data.last_updated == request.time;
    }

    match /countries/{country} {
      allow write: if request.auth.uid != null;
    }
  }
}

Batas aturan keamanan

Dalam aturan keamanan untuk transaksi atau penulisan batch, ada batas 20 panggilan akses dokumen untuk keseluruhan operasi menyeluruh selain batas normal 10 panggilan untuk setiap operasi dokumen tunggal dalam batch.

Misalnya, pertimbangkan aturan berikut untuk aplikasi chat:

service cloud.firestore {
  match /databases/{db}/documents {
    function prefix() {
      return /databases/{db}/documents;
    }
    match /chatroom/{roomId} {
      allow read, write: if roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats
                            || exists(/$(prefix())/admins/$(request.auth.uid));
    }
    match /users/{userId} {
      allow read, write: if userId == request.auth.uid
                            || exists(/$(prefix())/admins/$(request.auth.uid));
    }
    match /admins/{userId} {
      allow read, write: if exists(/$(prefix())/admins/$(request.auth.uid));
    }
  }
}

Cuplikan di bawah ini mengilustrasikan jumlah panggilan akses dokumen yang digunakan untuk beberapa pola akses data:

// 0 document access calls used, because the rules evaluation short-circuits
// before the exists() call is invoked.
db.collection('user').doc('myuid').get(...);

// 1 document access call used. The maximum total allowed for this call
// is 10, because it is a single document request.
db.collection('chatroom').doc('mygroup').get(...);

// Initializing a write batch...
var batch = db.batch();

// 2 document access calls used, 10 allowed.
var group1Ref = db.collection("chatroom").doc("group1");
batch.set(group1Ref, {msg: "Hello, from Admin!"});

// 1 document access call used, 10 allowed.
var newUserRef = db.collection("users").doc("newuser");
batch.update(newUserRef, {"lastSignedIn": new Date()});

// 1 document access call used, 10 allowed.
var removedAdminRef = db.collection("admin").doc("otheruser");
batch.delete(removedAdminRef);

// The batch used a total of 2 + 1 + 1 = 4 document access calls, out of a total
// 20 allowed.
batch.commit();