In diesem Dokument werden die vier Methoden zum Schreiben von Daten in Ihre Firebase Realtime Database behandelt: „set“, „update“, „push“ und Transaktionsunterstützung.
Möglichkeiten zum Speichern von Daten
| set | Daten in einen definierten Pfad wie messages/users/<username> schreiben oder ersetzen |
| update | Einige der Schlüssel für einen definierten Pfad aktualisieren, ohne alle Daten zu ersetzen |
| push | Daten zu einer Liste in der Datenbank hinzufügen Jedes Mal, wenn Sie einen neuen Knoten in eine Liste einfügen, generiert Ihre Datenbank einen eindeutigen Schlüssel, z. B. messages/users/<unique-user-id>/<username>. |
| Transaktion | Verwenden Sie Transaktionen, wenn Sie mit komplexen Daten arbeiten, die durch gleichzeitige Aktualisierungen beschädigt werden könnten. |
Daten speichern
Der grundlegende Schreibvorgang in der Datenbank ist ein Set, bei dem neue Daten im angegebenen Datenbankverweis gespeichert werden. Alle vorhandenen Daten in diesem Pfad werden dabei ersetzt. Um „set“ zu verstehen, erstellen wir eine einfache Blogging-App. Die Daten für Ihre App werden unter dieser Datenbankreferenz gespeichert:
Java
final FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
// Import Admin SDK const { getDatabase } = require('firebase-admin/database'); // Get a database reference to our blog const db = getDatabase(); const ref = db.ref('server/saving-data/fireblog');
Python
# Import database module. from firebase_admin import db # Get a database reference to our blog. ref = db.reference('server/saving-data/fireblog')
Go
// Create a database client from App. client, err := app.Database(ctx) if err != nil { log.Fatalln("Error initializing database client:", err) } // Get a database reference to our blog. ref := client.NewRef("server/saving-data/fireblog")
Beginnen wir mit dem Speichern einiger Nutzerdaten. Wir speichern jeden Nutzer mit einem eindeutigen Nutzernamen sowie seinem vollständigen Namen und Geburtsdatum. Da jeder Nutzer einen eindeutigen Nutzernamen hat, ist es sinnvoll, hier die Methode „set“ anstelle der Methode „push“ zu verwenden, da Sie den Schlüssel bereits haben und keinen erstellen müssen.
Erstellen Sie zuerst eine Datenbankreferenz zu Ihren Nutzerdaten. Verwenden Sie dann set() / setValue(), um ein Nutzerobjekt mit dem Nutzernamen, dem vollständigen Namen und dem Geburtstag des Nutzers in der Datenbank zu speichern. Sie können einen String, eine Zahl, einen booleschen Wert, ein null, ein Array oder ein beliebiges JSON-Objekt festlegen. Wenn Sie null übergeben, werden die Daten am angegebenen Speicherort entfernt. In diesem Fall übergeben Sie ein Objekt:
Java
public static class User { public String date_of_birth; public String full_name; public String nickname; public User(String dateOfBirth, String fullName) { // ... } public User(String dateOfBirth, String fullName, String nickname) { // ... } } DatabaseReference usersRef = ref.child("users"); Map<String, User> users = new HashMap<>(); users.put("alanisawesome", new User("June 23, 1912", "Alan Turing")); users.put("gracehop", new User("December 9, 1906", "Grace Hopper")); usersRef.setValueAsync(users);
Node.js
const usersRef = ref.child('users'); usersRef.set({ alanisawesome: { date_of_birth: 'June 23, 1912', full_name: 'Alan Turing' }, gracehop: { date_of_birth: 'December 9, 1906', full_name: 'Grace Hopper' } });
Python
users_ref = ref.child('users') users_ref.set({ 'alanisawesome': { 'date_of_birth': 'June 23, 1912', 'full_name': 'Alan Turing' }, 'gracehop': { 'date_of_birth': 'December 9, 1906', 'full_name': 'Grace Hopper' } })
Go
// User is a json-serializable type. type User struct { DateOfBirth string `json:"date_of_birth,omitempty"` FullName string `json:"full_name,omitempty"` Nickname string `json:"nickname,omitempty"` } usersRef := ref.Child("users") err := usersRef.Set(ctx, map[string]*User{ "alanisawesome": { DateOfBirth: "June 23, 1912", FullName: "Alan Turing", }, "gracehop": { DateOfBirth: "December 9, 1906", FullName: "Grace Hopper", }, }) if err != nil { log.Fatalln("Error setting value:", err) }
Wenn ein JSON-Objekt in der Datenbank gespeichert wird, werden die Objekteigenschaften automatisch verschachtelt den untergeordneten Speicherorten der Datenbank zugeordnet. Wenn Sie jetzt die URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name aufrufen, wird der Wert „Alan Turing“ angezeigt. Sie können Daten auch direkt in einem untergeordneten Speicherort speichern:
Java
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing")); usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Node.js
const usersRef = ref.child('users'); usersRef.child('alanisawesome').set({ date_of_birth: 'June 23, 1912', full_name: 'Alan Turing' }); usersRef.child('gracehop').set({ date_of_birth: 'December 9, 1906', full_name: 'Grace Hopper' });
Python
users_ref.child('alanisawesome').set({ 'date_of_birth': 'June 23, 1912', 'full_name': 'Alan Turing' }) users_ref.child('gracehop').set({ 'date_of_birth': 'December 9, 1906', 'full_name': 'Grace Hopper' })
Go
if err := usersRef.Child("alanisawesome").Set(ctx, &User{ DateOfBirth: "June 23, 1912", FullName: "Alan Turing", }); err != nil { log.Fatalln("Error setting value:", err) } if err := usersRef.Child("gracehop").Set(ctx, &User{ DateOfBirth: "December 9, 1906", FullName: "Grace Hopper", }); err != nil { log.Fatalln("Error setting value:", err) }
Die beiden obigen Beispiele – das gleichzeitige Schreiben beider Werte als Objekt und das separate Schreiben in untergeordnete Speicherorte – führen dazu, dass dieselben Daten in Ihrer Datenbank gespeichert werden:
{
"users": {
"alanisawesome": {
"date_of_birth": "June 23, 1912",
"full_name": "Alan Turing"
},
"gracehop": {
"date_of_birth": "December 9, 1906",
"full_name": "Grace Hopper"
}
}
}
Im ersten Beispiel wird nur ein Ereignis auf Clients ausgelöst, die die Daten beobachten, während im zweiten Beispiel zwei Ereignisse ausgelöst werden. Beachten Sie, dass die erste Methode vorhandene Daten unter usersRef überschreibt, während die zweite Methode nur den Wert der einzelnen untergeordneten Knoten ändert und andere untergeordnete Knoten von usersRef unverändert lässt.
Gespeicherte Daten werden aktualisiert
Wenn Sie gleichzeitig in mehrere untergeordnete Elemente eines Datenbankspeicherorts schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, können Sie die Methode „update“ wie unten gezeigt verwenden:
Java
DatabaseReference hopperRef = usersRef.child("gracehop"); Map<String, Object> hopperUpdates = new HashMap<>(); hopperUpdates.put("nickname", "Amazing Grace"); hopperRef.updateChildrenAsync(hopperUpdates);
Node.js
const usersRef = ref.child('users'); const hopperRef = usersRef.child('gracehop'); hopperRef.update({ 'nickname': 'Amazing Grace' });
Python
hopper_ref = users_ref.child('gracehop') hopper_ref.update({ 'nickname': 'Amazing Grace' })
Go
hopperRef := usersRef.Child("gracehop") if err := hopperRef.Update(ctx, map[string]interface{}{ "nickname": "Amazing Grace", }); err != nil { log.Fatalln("Error updating child:", err) }
Dadurch werden die Daten von Grace aktualisiert und ihr Spitzname wird hinzugefügt. Wenn Sie hier „set“ statt „update“ verwendet hätten, wären sowohl full_name als auch date_of_birth aus Ihrem hopperRef gelöscht worden.
Der Firebase Realtime Database unterstützt auch Aktualisierungen über mehrere Pfade. Das bedeutet, dass mit „update“ jetzt Werte an mehreren Stellen in Ihrer Datenbank gleichzeitig aktualisiert werden können. Diese leistungsstarke Funktion hilft Ihnen, Ihre Daten zu denormalisieren. Mit Aktualisierungen mit mehreren Pfaden können Sie Grace und Alan gleichzeitig Spitznamen hinzufügen:
Java
Map<String, Object> userUpdates = new HashMap<>(); userUpdates.put("alanisawesome/nickname", "Alan The Machine"); userUpdates.put("gracehop/nickname", "Amazing Grace"); usersRef.updateChildrenAsync(userUpdates);
Node.js
const usersRef = ref.child('users'); usersRef.update({ 'alanisawesome/nickname': 'Alan The Machine', 'gracehop/nickname': 'Amazing Grace' });
Python
users_ref.update({ 'alanisawesome/nickname': 'Alan The Machine', 'gracehop/nickname': 'Amazing Grace' })
Go
if err := usersRef.Update(ctx, map[string]interface{}{ "alanisawesome/nickname": "Alan The Machine", "gracehop/nickname": "Amazing Grace", }); err != nil { log.Fatalln("Error updating children:", err) }
Nach dieser Aktualisierung wurden die Spitznamen von Alan und Grace hinzugefügt:
{
"users": {
"alanisawesome": {
"date_of_birth": "June 23, 1912",
"full_name": "Alan Turing",
"nickname": "Alan The Machine"
},
"gracehop": {
"date_of_birth": "December 9, 1906",
"full_name": "Grace Hopper",
"nickname": "Amazing Grace"
}
}
}Wenn Sie versuchen, Objekte zu aktualisieren, indem Sie Objekte mit den enthaltenen Pfaden schreiben, führt dies zu einem anderen Verhalten. Sehen wir uns an, was passiert, wenn Sie stattdessen versuchen, Grace und Alan so zu aktualisieren:
Java
Map<String, Object> userNicknameUpdates = new HashMap<>(); userNicknameUpdates.put("alanisawesome", new User(null, null, "Alan The Machine")); userNicknameUpdates.put("gracehop", new User(null, null, "Amazing Grace")); usersRef.updateChildrenAsync(userNicknameUpdates);
Node.js
const usersRef = ref.child('users'); usersRef.update({ 'alanisawesome': { 'nickname': 'Alan The Machine' }, 'gracehop': { 'nickname': 'Amazing Grace' } });
Python
users_ref.update({ 'alanisawesome': { 'nickname': 'Alan The Machine' }, 'gracehop': { 'nickname': 'Amazing Grace' } })
Go
if err := usersRef.Update(ctx, map[string]interface{}{ "alanisawesome": &User{Nickname: "Alan The Machine"}, "gracehop": &User{Nickname: "Amazing Grace"}, }); err != nil { log.Fatalln("Error updating children:", err) }
Dies führt zu einem anderen Verhalten, nämlich dem Überschreiben des gesamten /users-Knotens:
{
"users": {
"alanisawesome": {
"nickname": "Alan The Machine"
},
"gracehop": {
"nickname": "Amazing Grace"
}
}
}Callback für den Abschluss hinzufügen
In den Node.js- und Java-Admin-SDKs können Sie einen Abschluss-Callback hinzufügen, um zu erfahren, wann Ihre Daten übertragen wurden. Sowohl die „set“- als auch die „update“-Methode in diesen SDKs akzeptieren einen optionalen Abschluss-Callback, der aufgerufen wird, wenn der Schreibvorgang in die Datenbank übertragen wurde. Wenn der Aufruf aus irgendeinem Grund nicht erfolgreich war, wird dem Callback ein Fehlerobjekt übergeben, das die Ursache des Fehlers angibt. In den Python- und Go-Admin SDKs sind alle Schreibmethoden blockierend. Das bedeutet, dass die Schreibmethoden erst zurückgegeben werden, wenn die Schreibvorgänge in die Datenbank übernommen wurden.
Java
DatabaseReference dataRef = ref.child("data"); dataRef.setValue("I'm writing data", new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { if (databaseError != null) { System.out.println("Data could not be saved " + databaseError.getMessage()); } else { System.out.println("Data saved successfully."); } } });
Node.js
dataRef.set('I\'m writing data', (error) => { if (error) { console.log('Data could not be saved.' + error); } else { console.log('Data saved successfully.'); } });
Datenlisten speichern
Beim Erstellen von Datenlisten ist es wichtig, die Mehrbenutzer-Natur der meisten Anwendungen zu berücksichtigen und die Listenstruktur entsprechend anzupassen. Erweitern wir das obige Beispiel und fügen Ihrer App Blogposts hinzu. Ihr erster Instinkt könnte sein, mit „set“ untergeordnete Elemente mit automatisch inkrementierenden Ganzzahlindizes zu speichern, wie im Folgenden:
// NOT RECOMMENDED - use push() instead! { "posts": { "0": { "author": "gracehop", "title": "Announcing COBOL, a New Programming Language" }, "1": { "author": "alanisawesome", "title": "The Turing Machine" } } }
Wenn ein Nutzer einen neuen Beitrag hinzufügt, wird er als /posts/2 gespeichert. Das würde funktionieren, wenn nur ein einzelner Autor Beiträge hinzufügen würde. In Ihrer kollaborativen Blogging-Anwendung können jedoch viele Nutzer gleichzeitig Beiträge hinzufügen. Wenn zwei Autoren gleichzeitig in /posts/2 schreiben, wird einer der Beiträge vom anderen gelöscht.
Um dieses Problem zu lösen, bieten die Firebase-Clients eine push()-Funktion, die für jedes neue untergeordnete Element einen eindeutigen Schlüssel generiert. Durch die Verwendung eindeutiger untergeordneter Schlüssel können mehrere Clients gleichzeitig untergeordnete Elemente am selben Speicherort hinzufügen, ohne dass Schreibkonflikte auftreten.
Java
public static class Post { public String author; public String title; public Post(String author, String title) { // ... } } DatabaseReference postsRef = ref.child("posts"); DatabaseReference newPostRef = postsRef.push(); newPostRef.setValueAsync(new Post("gracehop", "Announcing COBOL, a New Programming Language")); // We can also chain the two calls together postsRef.push().setValueAsync(new Post("alanisawesome", "The Turing Machine"));
Node.js
const newPostRef = postsRef.push(); newPostRef.set({ author: 'gracehop', title: 'Announcing COBOL, a New Programming Language' }); // we can also chain the two calls together postsRef.push().set({ author: 'alanisawesome', title: 'The Turing Machine' });
Python
posts_ref = ref.child('posts') new_post_ref = posts_ref.push() new_post_ref.set({ 'author': 'gracehop', 'title': 'Announcing COBOL, a New Programming Language' }) # We can also chain the two calls together posts_ref.push().set({ 'author': 'alanisawesome', 'title': 'The Turing Machine' })
Go
// Post is a json-serializable type. type Post struct { Author string `json:"author,omitempty"` Title string `json:"title,omitempty"` } postsRef := ref.Child("posts") newPostRef, err := postsRef.Push(ctx, nil) if err != nil { log.Fatalln("Error pushing child node:", err) } if err := newPostRef.Set(ctx, &Post{ Author: "gracehop", Title: "Announcing COBOL, a New Programming Language", }); err != nil { log.Fatalln("Error setting value:", err) } // We can also chain the two calls together if _, err := postsRef.Push(ctx, &Post{ Author: "alanisawesome", Title: "The Turing Machine", }); err != nil { log.Fatalln("Error pushing child node:", err) }
Der eindeutige Schlüssel basiert auf einem Zeitstempel, sodass Listenelemente automatisch chronologisch sortiert werden. Da Firebase für jeden Blogpost einen eindeutigen Schlüssel generiert, treten keine Schreibkonflikte auf, wenn mehrere Nutzer gleichzeitig einen Post hinzufügen. Ihre Datenbankdaten sehen jetzt so aus:
{
"posts": {
"-JRHTHaIs-jNPLXOQivY": {
"author": "gracehop",
"title": "Announcing COBOL, a New Programming Language"
},
"-JRHTHaKuITFIhnj02kE": {
"author": "alanisawesome",
"title": "The Turing Machine"
}
}
}
In JavaScript, Python und Go ist das Muster, push() und dann sofort set() aufzurufen, so gängig, dass Sie die beiden Aufrufe im Firebase SDK kombinieren können. Dazu übergeben Sie die festzulegenden Daten direkt an push():
Java
// No Java equivalentNode.js
// This is equivalent to the calls to push().set(...) above postsRef.push({ author: 'gracehop', title: 'Announcing COBOL, a New Programming Language' });;
Python
# This is equivalent to the calls to push().set(...) above posts_ref.push({ 'author': 'gracehop', 'title': 'Announcing COBOL, a New Programming Language' })
Go
if _, err := postsRef.Push(ctx, &Post{ Author: "gracehop", Title: "Announcing COBOL, a New Programming Language", }); err != nil { log.Fatalln("Error pushing child node:", err) }
Eindeutigen Schlüssel abrufen, der von push() generiert wurde
Durch den Aufruf von push() wird ein Verweis auf den neuen Datenpfad zurückgegeben, mit dem Sie den Schlüssel abrufen oder Daten dafür festlegen können. Der folgende Code führt zu denselben Daten wie im obigen Beispiel, aber jetzt haben wir Zugriff auf den generierten eindeutigen Schlüssel:
Java
// Generate a reference to a new location and add some data using push() DatabaseReference pushedPostRef = postsRef.push(); // Get the unique ID generated by a push() String postId = pushedPostRef.getKey();
Node.js
// Generate a reference to a new location and add some data using push() const newPostRef = postsRef.push(); // Get the unique key generated by push() const postId = newPostRef.key;
Python
# Generate a reference to a new location and add some data using push() new_post_ref = posts_ref.push() # Get the unique key generated by push() post_id = new_post_ref.key
Go
// Generate a reference to a new location and add some data using Push() newPostRef, err := postsRef.Push(ctx, nil) if err != nil { log.Fatalln("Error pushing child node:", err) } // Get the unique key generated by Push() postID := newPostRef.Key
Wie Sie sehen, können Sie den Wert des eindeutigen Schlüssels aus Ihrer push()-Referenz abrufen.
Im nächsten Abschnitt Daten abrufen erfahren Sie, wie Sie diese Daten aus einer Firebase-Datenbank lesen.
Transaktionsdaten speichern
Wenn Sie mit komplexen Daten arbeiten, die durch gleichzeitige Änderungen beschädigt werden könnten, z. B. inkrementelle Zähler, bietet das SDK einen Transaktionsvorgang.
In Java und Node.js übergeben Sie dem Transaktionsvorgang zwei Callbacks: eine Aktualisierungsfunktion und einen optionalen Abschluss-Callback. In Python und Go ist der Transaktionsvorgang blockierend und akzeptiert daher nur die Aktualisierungsfunktion.
Die Aktualisierungsfunktion verwendet den aktuellen Status der Daten als Argument und sollte den neuen gewünschten Status zurückgeben, den Sie schreiben möchten. Wenn Sie beispielsweise die Anzahl der Upvotes für einen bestimmten Blogpost erhöhen möchten, können Sie eine Transaktion wie die folgende schreiben:
Java
DatabaseReference upvotesRef = ref.child("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes"); upvotesRef.runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { Integer currentValue = mutableData.getValue(Integer.class); if (currentValue == null) { mutableData.setValue(1); } else { mutableData.setValue(currentValue + 1); } return Transaction.success(mutableData); } @Override public void onComplete( DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) { System.out.println("Transaction completed"); } });
Node.js
const upvotesRef = db.ref('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes'); upvotesRef.transaction((current_value) => { return (current_value || 0) + 1; });
Python
def increment_votes(current_value): return current_value + 1 if current_value else 1 upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes') try: new_vote_count = upvotes_ref.transaction(increment_votes) print('Transaction completed') except db.TransactionAbortedError: print('Transaction failed to commit')
Go
fn := func(t db.TransactionNode) (interface{}, error) { var currentValue int if err := t.Unmarshal(¤tValue); err != nil { return nil, err } return currentValue + 1, nil } ref := client.NewRef("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes") if err := ref.Transaction(ctx, fn); err != nil { log.Fatalln("Transaction failed to commit:", err) }
Im obigen Beispiel wird geprüft, ob der Zähler null ist oder noch nicht erhöht wurde, da Transaktionen mit null aufgerufen werden können, wenn kein Standardwert geschrieben wurde.
Wenn der obige Code ohne Transaktionsfunktion ausgeführt worden wäre und zwei Clients versucht hätten, den Wert gleichzeitig zu erhöhen, hätten beide 1 als neuen Wert geschrieben. Das hätte zu einer Erhöhung um eins statt um zwei geführt.
Netzwerkverbindung und Offlineschreibvorgänge
Firebase Node.js- und Java-Clients verwalten ihre eigene interne Version aller aktiven Daten. Wenn Daten geschrieben werden, werden sie zuerst in diese lokale Version geschrieben. Der Client synchronisiert diese Daten dann mit der Datenbank und mit anderen Clients nach dem Best-Effort-Prinzip.
Daher lösen alle Schreibvorgänge in der Datenbank sofort lokale Ereignisse aus, noch bevor Daten in die Datenbank geschrieben wurden. Wenn Sie also eine Anwendung mit Firebase schreiben, bleibt Ihre App unabhängig von Netzwerklatenz oder Internetverbindung reaktionsfähig.
Sobald die Verbindung wiederhergestellt ist, erhalten wir die entsprechenden Ereignisse, sodass der Client den aktuellen Serverstatus „nachholt“, ohne dass benutzerdefinierter Code geschrieben werden muss.
Daten sichern
Die Firebase Realtime Database verfügt über eine Sicherheitssprache, mit der Sie festlegen können, welche Nutzer Lese- und Schreibzugriff auf verschiedene Knoten Ihrer Daten haben. Weitere Informationen dazu finden Sie unter Daten schützen.