Dieses Dokument behandelt die vier Methoden zum Schreiben von Daten in Ihre Firebase-Echtzeitdatenbank: Festlegen, Aktualisieren, Pushen und Transaktionsunterstützung.
Möglichkeiten zum Speichern von Daten
Satz | Schreiben oder ersetzen Sie Daten in einen definierten Pfad , wie beispielsweise messages/users/<username> |
aktualisieren | Aktualisieren Sie einige der Schlüssel für einen definierten Pfad, ohne alle Daten zu ersetzen |
drücken | Zu einer Liste von Daten in der Datenbank hinzufügen . Jedes Mal, wenn Sie einen neuen Knoten auf eine Liste schieben, generiert Ihre Datenbank einen eindeutigen Schlüssel, wie beispielsweise 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
Die grundlegende Datenbank-Schreiboperation ist ein Satz, der neue Daten in der angegebenen Datenbankreferenz speichert und alle vorhandenen Daten in diesem Pfad ersetzt. Um set zu verstehen, bauen 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')
Gehen
// 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 damit, einige Benutzerdaten zu speichern. Wir speichern jeden Benutzer unter einem eindeutigen Benutzernamen, und wir speichern auch seinen vollständigen Namen und sein Geburtsdatum. Da jeder Benutzer einen eindeutigen Benutzernamen haben wird, ist es sinnvoll, hier die Set-Methode anstelle der Push-Methode zu verwenden, da Sie den Schlüssel bereits haben und keinen erstellen müssen.
Erstellen Sie zunächst einen Datenbankbezug zu Ihren Benutzerdaten. Verwenden Sie dann set()
/ setValue()
, um ein Benutzerobjekt mit dem Benutzernamen, dem vollständigen Namen und dem Geburtstag des Benutzers in der Datenbank zu speichern. Sie können eine Zeichenfolge, eine Zahl, einen booleschen Wert, null
, ein Array oder ein beliebiges JSON-Objekt übergeben. Durch das Übergeben null
werden die Daten an der angegebenen Position entfernt. In diesem Fall übergeben Sie ihm 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' } })
Gehen
// 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 verschachtelten untergeordneten Speicherorten der Datenbank zugeordnet. Wenn Sie nun zur URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name navigieren, sehen wir 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' })
Gehen
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 – beide Werte gleichzeitig als Objekt schreiben und sie separat in untergeordnete Speicherorte schreiben – 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 auf Clients aus, die die Daten beobachten, während das zweite Beispiel zwei auslöst. Es ist wichtig zu beachten, dass, wenn bereits Daten bei usersRef
vorhanden waren, der erste Ansatz diese überschreiben würde, aber die zweite Methode nur den Wert jedes einzelnen untergeordneten Knotens ändern würde, während andere untergeordnete Knoten von usersRef
unverändert bleiben würden.
Aktualisieren gespeicherter Daten
Wenn Sie gleichzeitig in mehrere untergeordnete Knoten eines Datenbankspeicherorts schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, können Sie die Update-Methode 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' })
Gehen
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 Graces Daten aktualisiert, sodass sie ihren Spitznamen enthalten. Wenn Sie set here anstelle von update verwendet hätten, wären sowohl full_name
als auch date_of_birth
aus Ihrer hopperRef
gelöscht worden.
Die Firebase Realtime Database unterstützt auch Multipath-Updates. Das bedeutet, dass Update jetzt Werte an mehreren Stellen in Ihrer Datenbank gleichzeitig aktualisieren kann, eine leistungsstarke Funktion, mit der Sie Ihre Daten denormalisieren können. Mithilfe von Multipath-Updates 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' })
Gehen
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 diesem Update 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" } } }
Beachten Sie, dass der Versuch, Objekte zu aktualisieren, indem Objekte mit den enthaltenen Pfaden geschrieben werden, zu einem anderen Verhalten führt. Schauen wir uns an, was passiert, wenn Sie stattdessen versuchen, Grace und Alan auf diese Weise 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' } })
Gehen
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 /users
Knotens:
{ "users": { "alanisawesome": { "nickname": "Alan The Machine" }, "gracehop": { "nickname": "Amazing Grace" } } }
Hinzufügen eines Abschlussrückrufs
Wenn Sie in Node.js und Java Admin SDKs wissen möchten, wann Ihre Daten festgeschrieben wurden, können Sie einen Abschluss-Callback hinzufügen. Sowohl set- als auch update-Methoden in diesen SDKs nehmen einen optionalen Abschluss-Callback an, der aufgerufen wird, wenn der Schreibvorgang an die Datenbank übertragen wurde. Wenn der Aufruf aus irgendeinem Grund nicht erfolgreich war, wird dem Rückruf ein Fehlerobjekt übergeben, das angibt, warum der Fehler aufgetreten ist. In Python- und Go Admin-SDKs blockieren alle Schreibmethoden. Das heißt, die Schreibmethoden kehren erst zurück, wenn die Schreibvorgänge an die Datenbank übergeben 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.'); } });
Speichern von Datenlisten
Beim Erstellen von Datenlisten ist es wichtig, die Multi-User-Natur der meisten Anwendungen zu berücksichtigen und Ihre Listenstruktur entsprechend anzupassen. Lassen Sie uns das obige Beispiel erweitern und Blogbeiträge zu Ihrer App hinzufügen. Ihr erster Instinkt könnte sein, set zum Speichern von untergeordneten Elementen mit automatisch inkrementierenden Integer-Indizes wie dem folgenden zu verwenden:
// 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 Benutzer einen neuen Beitrag hinzufügt, wird dieser als /posts/2
gespeichert. Dies würde funktionieren, wenn nur ein einziger Autor Beiträge hinzufügen würde, aber in Ihrer kollaborativen Blogging-Anwendung können viele Benutzer gleichzeitig Beiträge hinzufügen. Wenn zwei Autoren gleichzeitig auf /posts/2
schreiben, dann würde einer der Posts vom anderen gelöscht.
Um dies zu lösen, stellen die Firebase-Clients eine push()
Funktion bereit, die einen eindeutigen Schlüssel für jedes neue untergeordnete Element generiert . Durch die Verwendung eindeutiger untergeordneter Schlüssel können mehrere Clients gleichzeitig untergeordnete Schlüssel zum selben Speicherort hinzufügen, ohne sich Gedanken über Schreibkonflikte machen 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' })
Gehen
// 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 geordnet werden. Da Firebase für jeden Blogbeitrag einen eindeutigen Schlüssel generiert, treten keine Schreibkonflikte auf, wenn mehrere Benutzer gleichzeitig einen Beitrag hinzufügen. Ihre Datenbankdaten sehen nun 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 des Aufrufs von push()
und des sofortigen Aufrufs von set()
so üblich, dass Sie das Firebase SDK kombinieren können, indem Sie die zu setzenden Daten wie folgt direkt an push()
übergeben:
Java
// No Java equivalent
Node.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' })
Gehen
if _, err := postsRef.Push(ctx, &Post{ Author: "gracehop", Title: "Announcing COBOL, a New Programming Language", }); err != nil { log.Fatalln("Error pushing child node:", err) }
Abrufen des von push() generierten eindeutigen Schlüssels
Der Aufruf von push()
gibt eine Referenz auf den neuen Datenpfad zurück, den Sie verwenden können, um den Schlüssel abzurufen oder Daten darauf zu setzen. Der folgende Code führt zu denselben Daten wie das obige 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
Gehen
// 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 zum Abrufen von Daten erfahren Sie, wie Sie diese Daten aus einer Firebase-Datenbank lesen.
Speichern von Transaktionsdaten
Bei der Arbeit mit komplexen Daten, die durch gleichzeitige Änderungen beschädigt werden könnten, wie z. B. inkrementelle Zähler, bietet das SDK eine Transaktionsoperation .
In Java und Node.js geben Sie der Transaktionsoperation zwei Callbacks: eine Aktualisierungsfunktion und einen optionalen Abschluss-Callback. In Python und Go blockiert die Transaktionsoperation und akzeptiert daher nur die Update-Funktion.
Die Update-Funktion nimmt den aktuellen Zustand der Daten als Argument und sollte den neuen gewünschten Zustand zurückgeben, den Sie schreiben möchten. Wenn Sie beispielsweise die Anzahl der Upvotes für einen bestimmten Blogbeitrag 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')
Gehen
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) }
Das obige Beispiel prüft, ob der Zähler null
ist oder noch nicht inkrementiert wurde, da Transaktionen mit null
aufgerufen werden können, wenn kein Standardwert geschrieben wurde.
Wenn der obige Code ohne eine Transaktionsfunktion ausgeführt worden wäre und zwei Clients versuchten, ihn gleichzeitig zu inkrementieren, würden sie beide 1
als neuen Wert schreiben, was zu einem Inkrement anstelle von zwei führen würde.
Netzwerkkonnektivität und Offline-Schreibvorgä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 dann diese Daten mit der Datenbank und mit anderen Clients nach bestem Wissen und Gewissen.
Daher lösen alle Schreibvorgänge in die Datenbank sofort lokale Ereignisse aus, bevor überhaupt Daten in die Datenbank geschrieben wurden. Das bedeutet, dass Ihre App beim Schreiben einer Anwendung mit Firebase unabhängig von Netzwerklatenz oder Internetverbindung reaktionsfähig bleibt.
Sobald die Konnektivität wiederhergestellt ist, erhalten wir die entsprechenden Ereignisse, sodass der Client den aktuellen Serverstatus „einholt“, ohne benutzerdefinierten Code schreiben zu müssen.
Sicherung Ihrer Daten
Die Firebase-Echtzeitdatenbank verfügt über eine Sicherheitssprache, mit der Sie festlegen können, welche Benutzer Lese- und Schreibzugriff auf verschiedene Knoten Ihrer Daten haben. Sie können mehr darüber in Sichern Sie Ihre Daten lesen.