Daten speichern

In diesem Dokument werden die vier Methoden zum Schreiben von Daten in Firebase Realtime Database beschrieben: Festlegen, Aktualisieren, Pushen und Transaktionen.

Möglichkeiten zum Einsparen von Daten

set Daten in einen definierten Pfad schreiben oder ersetzen, z. B. messages/users/<username>
update Einige 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 die Datenbank einen eindeutigen Schlüssel, z. B. messages/users/<unique-user-id>/<username>.
Transaktion Transaktionen verwenden, wenn Sie mit komplexen Daten arbeiten, die durch gleichzeitige Aktualisierungen beschädigt werden könnten

Daten speichern

Der grundlegende Datenbankschreibvorgang ist ein Satz, bei dem neue Daten in der angegebenen Datenbankreferenz gespeichert werden, wobei alle vorhandenen Daten unter diesem Pfad ersetzt werden. Um die Funktion „Set“ zu verstehen, erstellen wir eine einfache Blog-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 seinem 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. Speichern Sie dann mit set() / setValue() ein Nutzerobjekt mit dem Nutzernamen, dem vollständigen Namen und dem Geburtstag des Nutzers in der Datenbank. 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 an der angegebenen Stelle 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'
    }
})
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, sehen Sie den Wert „Alan Turing“. 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)
}

In den beiden obigen Beispielen werden dieselben Daten in Ihrer Datenbank gespeichert, wenn Sie beide Werte gleichzeitig als Objekt und separat in untergeordnete Standorte schreiben:

{
  "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 werden hingegen zwei Ereignisse ausgelöst. Wichtig: Wenn unter usersRef bereits Daten vorhanden sind, werden diese mit der ersten Methode überschrieben. Mit der zweiten Methode werden jedoch nur die Werte der einzelnen untergeordneten Knoten geändert, während die anderen untergeordneten Knoten von usersRef unverändert bleiben.

Gespeicherte Daten aktualisieren

Wenn Sie in mehrere untergeordnete Knoten eines Datenbankstandorts gleichzeitig schreiben möchten, ohne andere untergeordnete Knoten zu überschreiben, können Sie die Aktualisierungsmethode 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 um ihren Spitznamen ergänzt. Wenn Sie „Hier festlegen“ anstelle von „Aktualisieren“ verwendet hätten, wären sowohl full_name als auch date_of_birth aus Ihrer hopperRef gelöscht worden.

Der Firebase Realtime Database unterstützt auch Multipath-Updates. Das bedeutet, dass bei einer Aktualisierung jetzt Werte an mehreren Speicherorten in Ihrer Datenbank gleichzeitig aktualisiert werden können. Diese leistungsstarke Funktion ermöglicht die Denormalisierung Ihrer Daten. Mit Multipath-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 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"
    }
  }
}

Wenn Sie versuchen, Objekte durch Schreiben von Objekten mit den enthaltenen Pfaden zu aktualisieren, 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)
}

Das 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

Wenn Sie in den Node.js- und Java Admin SDKs wissen möchten, wann Ihre Daten verbindlich gespeichert wurden, können Sie einen Abschluss-Callback hinzufügen. Sowohl die Set- als auch die Update-Methoden in diesen SDKs nehmen einen optionalen Abschluss-Callback entgegen, der aufgerufen wird, wenn die Daten in der Datenbank verbindlich gespeichert wurden. Wenn der Aufruf aus irgendeinem Grund fehlgeschlagen ist, 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 heißt, die Schreibmethoden geben erst dann ein Ergebnis zurück, wenn die Schreibvorgänge in der Datenbank verbindlich festgelegt 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 Mehrfachnutzung der meisten Anwendungen zu berücksichtigen und die Listenstruktur entsprechend anzupassen. Kommen wir nun zum obigen Beispiel und fügen Ihrer App Blogposts hinzu. Ihr erster Impuls könnte sein, dass Sie „set“ verwenden, um untergeordnete Elemente mit automatisch inkrementierenden Ganzzahlindexen zu speichern. 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 dieser als /posts/2 gespeichert. Das würde funktionieren, wenn nur ein Autor Beiträge hinzufügen würde. In Ihrer Anwendung für das gemeinsame Bloggen können jedoch viele Nutzer gleichzeitig Beiträge hinzufügen. Wenn zwei Autoren gleichzeitig auf /posts/2 schreiben, wird 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 untergeordnete Element einen eindeutigen Schlüssel generiert. Durch die Verwendung eindeutiger untergeordneter Schlüssel können mehrere Clients demselben Speicherort gleichzeitig untergeordnete Elemente hinzufügen, ohne sich um Schreibkonflikte kümmern 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. Listenelemente werden daher automatisch chronologisch geordnet. 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() und dann sofort set() aufzurufen, so häufig, dass Sie sie mit dem Firebase SDK kombinieren können, indem Sie die Daten, die festgelegt werden sollen, direkt an push() übergeben. Gehen Sie dazu so vor:

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)
}

Den von push() generierten eindeutigen Schlüssel abrufen

Wenn Sie push() aufrufen, wird eine Referenz auf den neuen Datenpfad zurückgegeben, mit der Sie den Schlüssel abrufen oder Daten darauf festlegen können. Der folgende Code führt zu denselben Daten wie im Beispiel oben, 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önnen, z. B. mit inkrementellen Zählern, 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 daher wird nur die Update-Funktion akzeptiert.

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 Hochwählen 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(&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 obige Code ohne Transaktionsfunktion ausgeführt würde und zwei Clients gleichzeitig versuchen würden, ihn zu erhöhen, würden beide 1 als neuen Wert schreiben, was zu einer Erhöhung statt zu zwei Erhöhungen führt.

Netzwerkverbindung und Offline-Schreibvorgänge

Firebase Node.js- und Java-Clients verwalten ihre eigene interne Version aller aktiven Daten. Beim Schreiben von Daten werden sie zuerst in diese lokale Version geschrieben. Der Client synchronisiert diese Daten dann auf Best-Effort-Basis mit der Datenbank und mit anderen Clients.

Daher lösen alle Schreibvorgänge in die Datenbank lokale Ereignisse aus, noch bevor Daten in die Datenbank geschrieben werden. Wenn Sie also eine Anwendung mit Firebase erstellen, bleibt Ihre App unabhängig von der Netzwerklatenz oder Internetverbindung reaktionsschnell.

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

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 finden Sie unter Daten schützen.