Daten speichern

Dieses Dokument behandelt die vier Methoden zum Schreiben von Daten in Ihre Firebase-Echtzeitdatenbank: Set, Update, Push und Transaktionsunterstützung.

Möglichkeiten zum Speichern von Daten

Satz Schreiben oder ersetzen Sie Daten in einen definierten Pfad , z. B. messages/users/<username>
aktualisieren Aktualisieren Sie einige Schlüssel für einen definierten Pfad, ohne alle Daten zu ersetzen
drücken Zu einer Datenliste in der Datenbank hinzufügen . Jedes Mal, wenn Sie einen neuen Knoten in eine Liste verschieben, 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

Bei der grundlegenden Datenbankschreiboperation handelt es sich um einen Satz, der neue Daten in der angegebenen Datenbankreferenz speichert und alle vorhandenen Daten in diesem Pfad 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')
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 mit dem Speichern einiger Benutzerdaten. Wir speichern jeden Benutzer mit einem eindeutigen Benutzernamen sowie seinen vollständigen Namen und sein Geburtsdatum. Da jeder Benutzer einen eindeutigen Benutzernamen hat, ist es sinnvoll, hier die Set-Methode anstelle der Push-Methode zu verwenden, da Sie bereits über den Schlüssel verfügen und keinen erstellen müssen.

Erstellen Sie zunächst einen Datenbankverweis auf Ihre 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 einen String, eine Zahl, einen booleschen Wert, null , ein Array oder ein beliebiges JSON-Objekt übergeben. Durch die Übergabe null werden die Daten am angegebenen Speicherort 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 verschachtelt den 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 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 auf Clients aus, die die Daten überwachen, während das zweite Beispiel zwei auslöst. Es ist wichtig zu beachten, dass der erste Ansatz die Daten überschreiben würde, wenn bereits Daten unter usersRef vorhanden wären, die zweite Methode jedoch nur den Wert jedes einzelnen untergeordneten Knotens ändern würde, während andere untergeordnete Knoten von usersRef unverändert bleiben würden.

Gespeicherte Daten aktualisieren

Wenn Sie gleichzeitig auf mehrere untergeordnete Knoten eines Datenbankspeicherorts schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, können Sie die unten gezeigte Aktualisierungsmethode 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 und enthalten ihren Spitznamen. 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-Echtzeitdatenbank unterstützt auch Multipfad-Updates. Das bedeutet, dass update nun Werte an mehreren Stellen in Ihrer Datenbank gleichzeitig aktualisieren kann, eine leistungsstarke Funktion, die Ihnen bei der Denormalisierung Ihrer Daten hilft. Mithilfe von Multipfad-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 sowohl Alan als auch Grace ihre Spitznamen 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 durch Schreiben von Objekten mit den enthaltenen Pfaden zu aktualisieren, zu einem unterschiedlichen 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 unterschiedlichen Verhalten, nämlich dem Ü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 Abschlussrückruf hinzufügen. Sowohl die Set- als auch die Update-Methode in diesen SDKs akzeptieren einen optionalen Abschlussrückruf, der aufgerufen wird, wenn der Schreibvorgang in der Datenbank festgeschrieben 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 in der Datenbank festgeschrieben 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, den Mehrbenutzercharakter der meisten Anwendungen zu berücksichtigen und Ihre Listenstruktur entsprechend anzupassen. Ergänzend zum obigen Beispiel fügen wir Ihrer App Blogbeiträge hinzu. Ihr erster Instinkt könnte sein, set zu verwenden, um untergeordnete Elemente mit automatisch inkrementierenden Integer-Indizes 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 Benutzer einen neuen Beitrag hinzufügt, wird dieser als /posts/2 gespeichert. Dies würde funktionieren, wenn nur ein einzelner 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, würde einer der Beiträge vom anderen gelöscht.

Um dieses Problem zu lösen, stellen die Firebase-Clients eine push() Funktion bereit, die für jedes neue Kind einen eindeutigen Schlüssel generiert . Durch die Verwendung eindeutiger untergeordneter Schlüssel können mehrere Clients gleichzeitig untergeordnete Elemente 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 sortiert werden. Da Firebase für jeden Blog-Beitrag einen eindeutigen Schlüssel generiert, kommt es nicht zu Schreibkonflikten, 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, push() aufzurufen und dann sofort set() aufzurufen, so verbreitet, dass Sie mit dem Firebase SDK diese 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)
}

Erhalten des von push() generierten eindeutigen Schlüssels

Der Aufruf von push() gibt einen Verweis auf den neuen Datenpfad zurück, den Sie verwenden können, um den Schlüssel abzurufen oder Daten darauf festzulegen. 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
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 Ihrer push() -Referenz entnehmen.

Im nächsten Abschnitt zum Abrufen von Daten erfahren Sie, wie Sie diese Daten aus einer Firebase-Datenbank lesen.

Transaktionsdaten speichern

Bei der Arbeit mit komplexen Daten, die durch gleichzeitige Änderungen, wie z. B. inkrementelle Zähler, beschädigt werden könnten, stellt das SDK eine Transaktionsoperation bereit.

In Java und Node.js geben Sie der Transaktionsoperation zwei Rückrufe: eine Aktualisierungsfunktion und einen optionalen Abschlussrückruf. In Python und Go ist der Transaktionsvorgang blockierend und akzeptiert daher nur die Aktualisierungsfunktion.

Die Aktualisierungsfunktion verwendet 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 Blog-Beitrag 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(&currentValue); 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, würden beide 1 als neuen Wert schreiben, was zu einem Inkrement statt zwei führte.

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 diese Daten dann nach dem Best-Effort-Prinzip mit der Datenbank und mit anderen Clients.

Dadurch 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 der Netzwerklatenz oder der Internetverbindung reaktionsfähig bleibt.

Sobald die Konnektivität wiederhergestellt ist, erhalten wir die entsprechenden Ereignisse, sodass der Client den aktuellen Serverstatus „aufholen“ kann, ohne dass benutzerdefinierter Code geschrieben werden muss.

Sicherung Ihrer Daten

Die Firebase-Echtzeitdatenbank verfügt über eine Sicherheitssprache, mit der Sie definieren können, welche Benutzer Lese- und Schreibzugriff auf verschiedene Knoten Ihrer Daten haben. Weitere Informationen hierzu finden Sie unter „Sichern Sie Ihre Daten“ .