Daten speichern

In diesem Dokument werden die vier Methoden zum Schreiben von Daten in Ihre Firebase Realtime Database beschrieben: „set“, „update“, „push“ und Unterstützung von Transaktionen.

Möglichkeiten zum Sparen 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 Einer Liste von Daten in der Datenbank hinzufügen Jedes Mal, wenn Sie einen neuen Knoten in eine Liste einfügen, wird in Ihrer Datenbank ein eindeutiger Schlüssel wie messages/users/<unique-user-id>/<username> generiert.
Transaktion Transaktionen verwenden, wenn Sie mit komplexen Daten arbeiten, die durch gleichzeitige Aktualisierungen beschädigt werden könnten

Daten speichern

Der grundlegende Datenbank-Schreibvorgang ist ein Set, mit dem neue Daten in der angegebenen Datenbankreferenz gespeichert werden. Alle vorhandenen Daten an diesem Pfad werden dabei ersetzt. Um das 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 mit 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 für Ihre 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, null, ein Array oder ein beliebiges JSON-Objekt übergeben. Wenn Sie null übergeben, werden die Daten am angegebenen Ort 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 in einer verschachtelten Struktur den untergeordneten Datenbankstandorten 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 an einem untergeordneten Standort 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 – beide Werte gleichzeitig als Objekt schreiben und sie separat an untergeordnete Standorte 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"
    }
  }
}

Im ersten Beispiel wird nur ein Ereignis auf Clients ausgelöst, die die Daten beobachten, im zweiten Beispiel jedoch zwei. Wenn bereits Daten unter usersRef vorhanden sind, 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 Elemente von usersRef unverändert bleiben.

Gespeicherte Daten aktualisieren

Wenn Sie gleichzeitig in mehrere untergeordnete Elemente eines Datenbankstandorts 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 Graces Daten aktualisiert, sodass sie ihren Spitznamen enthalten. Wenn Sie hier „set“ anstelle von „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 Updates mit mehreren Pfaden. Das bedeutet, dass mit „update“ jetzt Werte an mehreren Stellen in Ihrer Datenbank gleichzeitig aktualisiert werden können. Das ist eine leistungsstarke Funktion, mit der Sie Ihre Daten denormalisieren können. Mit Multi-Path-Updates 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 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"
    }
  }
}

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 Grace und Alan stattdessen so 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 /users-Knotens:

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}

Abschluss-Callback hinzufügen

In den Node.js- und Java-Admin-SDKs können Sie einen Completion-Callback hinzufügen, um zu erfahren, wann Ihre Daten übernommen wurden. Sowohl die Set- als auch die Update-Methoden in diesen SDKs akzeptieren einen optionalen Completion-Callback, der aufgerufen wird, wenn der Schreibvorgang in der Datenbank ausgeführt 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 Python- und Go-Admin-SDKs sind alle Schreibmethoden blockierend. Das bedeutet, dass die Schreibmethoden erst zurückgegeben werden, 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 Datenlisten ist es wichtig, die Multi-User-Natur der meisten Anwendungen zu berücksichtigen und die Listenstruktur entsprechend anzupassen. Wir erweitern das obige Beispiel und fügen Ihrer App Blogbeiträge hinzu. Ihr erster Impuls wäre vielleicht, „set“ zu verwenden, um untergeordnete Elemente mit automatisch inkrementierenden Ganzzahlindexen 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, würde einer der Beiträge vom anderen gelöscht werden.

Um dieses Problem zu beheben, stellen Firebase-Clients eine push()-Funktion bereit, 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 Ort hinzufügen, ohne dass es zu Schreibkonflikten kommt.

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 Blogbeitrag einen eindeutigen Schlüssel generiert, treten keine Schreibkonflikte auf, wenn mehrere Nutzer gleichzeitig einen Beitrag hinzufügen. Die 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(), so häufig, dass Sie die beiden mit dem Firebase SDK kombinieren können, indem Sie die festzulegenden Daten 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'
})
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

Beim Aufrufen 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 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

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 geben Sie für den Transaktionsvorgang zwei Callbacks an: eine Update-Funktion und einen optionalen Completion-Callback. In Python und Go ist der Transaktionsvorgang blockierend und akzeptiert daher nur die Update-Funktion.

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 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')
Go
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 inkrementiert wurde, da Transaktionen mit null aufgerufen werden können, wenn kein Standardwert geschrieben wurde.

Wenn der oben genannte Code ohne Transaktionsfunktion ausgeführt worden wäre und zwei Clients gleichzeitig versucht hätten, den Wert 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 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.

Daher werden durch alle Schreibvorgänge in die Datenbank sofort lokale Ereignisse ausgelöst, bevor überhaupt Daten in die Datenbank geschrieben wurden. Wenn Sie eine Anwendung mit Firebase schreiben, bleibt Ihre App also unabhängig von der Netzwerklatenz oder der Internetverbindung reaktionsfähig.

Sobald die Verbindung wiederhergestellt ist, erhalten wir die entsprechenden Ereignisse, damit der Client den aktuellen Serverstatus erreicht, ohne dass Sie benutzerdefinierten Code schreiben müssen.

Daten schützen

Die Firebase Realtime Database hat 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.