In diesem Dokument werden die vier Methoden zum Schreiben von Daten in Ihre Firebase Realtime Database behandelt: „set“, „update“, „push“ und Transaktionen.
Möglichkeiten zum Speichern von Daten
| set | Daten in einen definierten Pfad schreiben oder ersetzen, z. B. messages/users/<username> |
| update | Einige der Schlüssel für einen definierten Pfad aktualisieren, ohne alle Daten zu ersetzen |
| push | Einer Liste von Daten 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> |
| transaction | Transaktionen verwenden, wenn Sie mit komplexen Daten arbeiten, die durch gleichzeitige Aktualisierungen beschädigt werden könnten |
Daten speichern
Der grundlegende Schreibvorgang für die Datenbank ist „set“. Damit werden neue Daten in der angegebenen Datenbankreferenz gespeichert und alle vorhandenen Daten an diesem Pfad ersetzt. Um „set“ zu veranschaulichen, 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 „set“ einen String, eine Zahl, einen booleschen Wert, null, ein Array oder ein beliebiges JSON-Objekt übergeben. 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 auf verschachtelte Weise 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, sehen Sie den Wert „Alan Turing“. Sie können Daten auch direkt an 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 oben genannten Beispiele – das gleichzeitige Schreiben beider Werte als Objekt und das separate Schreiben an 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"
}
}
}
Das erste Beispiel löst nur ein Ereignis bei Clients aus, die die Daten beobachten, während das zweite Beispiel
zwei Ereignisse auslöst. Wenn bereits Daten unter usersRef vorhanden waren, werden sie beim ersten Ansatz
überschrieben. Bei der zweiten Methode wird jedoch nur der Wert jedes einzelnen untergeordneten Knotens geändert, während
andere untergeordnete Knoten von usersRef unverändert bleiben.
Gespeicherte Daten aktualisieren
Wenn Sie gleichzeitig in mehrere untergeordnete Knoten eines Datenbankspeicherorts schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, können Sie die Methode „update“ verwenden, wie unten gezeigt:
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 hinzugefügt. Wenn Sie hier „set“ anstelle von „update“ verwendet hätten,
wären sowohl full_name als auch date_of_birth aus hopperRef gelöscht worden.
Die Firebase Realtime Database unterstützt auch Aktualisierungen mit mehreren Pfaden. Das bedeutet, dass mit „update“ jetzt gleichzeitig Werte an mehreren Speicherorten in Ihrer Datenbank aktualisiert werden können. Dies ist eine leistungsstarke Funktion, mit der Sie Ihre Daten denormalisieren können. Mit Aktualisierungen mit mehreren Pfaden können Sie sowohl Grace als auch 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 haben sowohl Alan als auch Grace Spitznamen:
{
"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 Pfaden schreiben, führt dies zu einem anderen Verhalten. Sehen wir uns an, was passiert, wenn Sie Grace und Alan stattdessen auf diese Weise 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 zum Überschreiben des gesamten Knotens /users:
{
"users": {
"alanisawesome": {
"nickname": "Alan The Machine"
},
"gracehop": {
"nickname": "Amazing Grace"
}
}
}Abschluss-Callback hinzufügen
In den Admin SDKs für Node.js und Java können Sie einen Abschluss-Callback hinzufügen, wenn Sie wissen möchten, wann Ihre Daten übernommen wurden. Sowohl die Methoden „set“ als auch „update“ in diesen SDKs verwenden einen optionalen Abschluss-Callback, der aufgerufen wird wenn der Schreibvorgang in der Datenbank übernommen wurde. Wenn der Aufruf aus irgendeinem Grund nicht erfolgreich war, wird dem Callback ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist. In den Admin SDKs für Python und Go sind alle Schreibmethoden blockierend. Das heißt, die Schreibmethoden geben erst dann einen Wert zurück , wenn die Schreibvorgänge in der 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.'); } });
Listen mit Daten speichern
Beim Erstellen von Listen mit Daten müssen Sie die Mehrere Nutzer-Natur der meisten Anwendungen berücksichtigen und die Listenstruktur entsprechend anpassen. Erweitern wir das obige Beispiel und fügen wir Blogposts zu Ihrer App hinzu. Ihr erster Gedanke wäre vielleicht, „set“ zu verwenden, um untergeordnete Knoten mit automatisch inkrementierenden Ganzzahlindizes zu speichern, wie im folgenden Beispiel:
// 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 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 beheben, stellen die Firebase-Clients eine push() Funktion bereit, die für jeden neuen untergeordneten Knoten einen
eindeutigen Schlüssel generiert. Durch die Verwendung eindeutiger untergeordneter Schlüssel können mehrere Clients gleichzeitig untergeordnete Knoten am selben Speicherort hinzufügen, ohne sich um Schreibkonflikte sorgen zu müssen.
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 Beitrag 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() aufzurufen und dann sofort set() aufzurufen, so häufig, dass Sie sie mit dem Firebase SDK kombinieren können, indem Sie die festzulegenden Daten direkt an push() übergeben:
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
Wenn Sie push() aufrufen, wird eine Referenz zum neuen Datenpfad zurückgegeben, mit der Sie den Schlüssel abrufen oder Daten festlegen können. Der folgende Code führt zu denselben Daten wie im obigen Beispiel, aber jetzt haben wir Zugriff auf den eindeutigen Schlüssel, der generiert wurde:
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
Beim Arbeiten mit komplexen Daten, die durch gleichzeitige Änderungen beschädigt werden könnten, z. B. inkrementelle Zähler, bietet das SDK einen Transaktionsvorgang.
In Java und Node.js geben 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, schreiben Sie eine Transaktion wie die folgende:
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, ihn
gleichzeitig zu erhöhen, hätten beide 1 als neuen Wert geschrieben, was zu einer Erhöhung anstelle von zwei geführt hätte.
Netzwerkverbindung und Offlineschreibvorgänge
Firebase-Clients für Node.js und Java verwalten eine eigene interne Version aller aktiven Daten. Wenn Daten geschrieben werden, werden sie zuerst in diese lokale Version geschrieben. Der Client synchronisiert diese Daten dann nach dem Best-Effort-Prinzip mit der Datenbank und mit anderen Clients.
Daher lösen alle Schreibvorgänge in der Datenbank sofort lokale Ereignisse aus, bevor überhaupt Daten in die Datenbank geschrieben wurden. Wenn Sie also eine Anwendung mit Firebase schreiben, bleibt Ihre App unabhängig von der Netzwerklatenz oder der Internetverbindung reaktionsschnell.
Sobald die Verbindung wiederhergestellt ist, erhalten wir die entsprechenden Ereignisse, sodass der Client den aktuellen Serverstatus erreicht , ohne dass benutzerdefinierter Code geschrieben werden muss.
Daten schützen
Die Firebase Realtime Database hat eine Sicherheitssprache, mit der Sie definieren können, welche Nutzer Lese- und Schreibzugriff auf verschiedene Knoten Ihrer Daten haben. Weitere Informationen finden Sie unter Daten schützen.