La sauvegarde des données

Ce document couvre les quatre méthodes d'écriture de données dans votre base de données en temps réel Firebase : prise en charge de la définition, de la mise à jour, du push et des transactions.

Façons de sauvegarder des données

ensemble Écrivez ou remplacez des données dans un chemin défini , comme messages/users/<username>
mise à jour Mettre à jour certaines clés d'un chemin défini sans remplacer toutes les données
pousser Ajouter à une liste de données dans la base de données. Chaque fois que vous placez un nouveau nœud sur une liste, votre base de données génère une clé unique, comme messages/users/<unique-user-id>/<username>
transaction Utilisez des transactions lorsque vous travaillez avec des données complexes qui pourraient être corrompues par des mises à jour simultanées

La sauvegarde des données

L'opération d'écriture de base de base de données est un ensemble qui enregistre les nouvelles données dans la référence de base de données spécifiée, en remplaçant toutes les données existantes à ce chemin. Pour comprendre cet ensemble, nous allons créer une application de blog simple. Les données de votre application sont stockées dans cette référence de base de données :

Java
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Noeud.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')
Aller
// 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")

Commençons par enregistrer quelques données utilisateur. Nous stockerons chaque utilisateur sous un nom d'utilisateur unique, ainsi que son nom complet et sa date de naissance. Étant donné que chaque utilisateur aura un nom d'utilisateur unique, il est logique d'utiliser la méthode set ici au lieu de la méthode push puisque vous disposez déjà de la clé et n'avez pas besoin d'en créer une.

Tout d’abord, créez une référence de base de données à vos données utilisateur. Utilisez ensuite set() / setValue() pour enregistrer un objet utilisateur dans la base de données avec le nom d'utilisateur, le nom complet et la date de naissance de l'utilisateur. Vous pouvez transmettre une chaîne, un nombre, un booléen, null , un tableau ou tout autre objet JSON. Passer null supprimera les données à l’emplacement spécifié. Dans ce cas, vous lui passerez un objet :

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);
Noeud.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'
    }
})
Aller

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

Lorsqu'un objet JSON est enregistré dans la base de données, les propriétés de l'objet sont automatiquement mappées aux emplacements enfants de la base de données de manière imbriquée. Maintenant, si vous accédez à l'URL https://docs-examples.firebaseio.com/server/ saving-data/fireblog/users/alanisawesome/full_name , nous verrons la valeur "Alan Turing". Vous pouvez également enregistrer les données directement dans un emplacement enfant :

Java
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing"));
usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Noeud.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'
})
Aller
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)
}

Les deux exemples ci-dessus - écrire les deux valeurs en même temps en tant qu'objet et les écrire séparément dans des emplacements enfants - entraîneront l'enregistrement des mêmes données dans votre base de données :

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper"
    }
  }
}

Le premier exemple ne déclenchera qu'un seul événement sur les clients qui surveillent les données, tandis que le deuxième exemple en déclenchera deux. Il est important de noter que si les données existaient déjà sur usersRef , la première approche les écraserait, mais la seconde méthode ne modifierait que la valeur de chaque nœud enfant distinct tout en laissant les autres enfants de usersRef inchangés.

Mise à jour des données enregistrées

Si vous souhaitez écrire sur plusieurs enfants d'un emplacement de base de données en même temps sans écraser les autres nœuds enfants, vous pouvez utiliser la méthode de mise à jour comme indiqué ci-dessous :

Java
DatabaseReference hopperRef = usersRef.child("gracehop");
Map<String, Object> hopperUpdates = new HashMap<>();
hopperUpdates.put("nickname", "Amazing Grace");

hopperRef.updateChildrenAsync(hopperUpdates);
Noeud.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'
})
Aller
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

Cela mettra à jour les données de Grace pour inclure son surnom. Si vous aviez utilisé set here au lieu de update, cela aurait supprimé à la fois full_name et date_of_birth de votre hopperRef .

La base de données Firebase Realtime prend également en charge les mises à jour multi-chemins. Cela signifie que la mise à jour peut désormais mettre à jour les valeurs à plusieurs emplacements de votre base de données en même temps, une fonctionnalité puissante qui vous aide à dénormaliser vos données . Grâce aux mises à jour multi-chemins, vous pouvez ajouter des surnoms à Grace et à Alan en même temps :

Java
Map<String, Object> userUpdates = new HashMap<>();
userUpdates.put("alanisawesome/nickname", "Alan The Machine");
userUpdates.put("gracehop/nickname", "Amazing Grace");

usersRef.updateChildrenAsync(userUpdates);
Noeud.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'
})
Aller
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)
}

Après cette mise à jour, Alan et Grace ont vu leurs surnoms ajoutés :

{
  "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"
    }
  }
}

Notez qu'essayer de mettre à jour des objets en écrivant des objets avec les chemins inclus entraînera un comportement différent. Jetons un coup d'œil à ce qui se passe si vous essayez plutôt de mettre à jour Grace et Alan de cette façon :

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);
Noeud.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'
    }
})
Aller
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)
}

Cela entraîne un comportement différent, à savoir l'écrasement de l'intégralité du nœud /users :

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

Ajout d'un rappel d'achèvement

Dans les SDK Node.js et Java Admin, si vous souhaitez savoir quand vos données ont été validées, vous pouvez ajouter un rappel d'achèvement. Les méthodes set et update de ces SDK nécessitent un rappel d’achèvement facultatif qui est appelé lorsque l’écriture a été validée dans la base de données. Si l'appel a échoué pour une raison quelconque, le rappel reçoit un objet d'erreur indiquant la raison de l'échec. Dans les SDK Python et Go Admin, toutes les méthodes d’écriture sont bloquantes. Autrement dit, les méthodes d'écriture ne sont renvoyées que lorsque les écritures sont validées dans la base de données.

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.");
    }
  }
});
Noeud.js
dataRef.set('I\'m writing data', (error) => {
  if (error) {
    console.log('Data could not be saved.' + error);
  } else {
    console.log('Data saved successfully.');
  }
});

Enregistrement de listes de données

Lors de la création de listes de données, il est important de garder à l’esprit la nature multi-utilisateurs de la plupart des applications et d’ajuster la structure de votre liste en conséquence. En développant l'exemple ci-dessus, ajoutons des articles de blog à votre application. Votre premier réflexe pourrait être d'utiliser set pour stocker les enfants avec des index entiers auto-incrémentés, comme suit :

// NOT RECOMMENDED - use push() instead!
{
  "posts": {
    "0": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "1": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Si un utilisateur ajoute une nouvelle publication, elle sera stockée sous /posts/2 . Cela fonctionnerait si un seul auteur ajoutait des articles, mais dans votre application de blog collaboratif, de nombreux utilisateurs peuvent ajouter des articles en même temps. Si deux auteurs écrivent simultanément dans /posts/2 , alors l'un des messages sera supprimé par l'autre.

Pour résoudre ce problème, les clients Firebase fournissent une fonction push() qui génère une clé unique pour chaque nouveau child . En utilisant des clés enfants uniques, plusieurs clients peuvent ajouter des enfants au même emplacement en même temps sans se soucier des conflits d'écriture.

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"));
Noeud.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'
})
Aller

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

La clé unique est basée sur un horodatage, de sorte que les éléments de la liste seront automatiquement classés par ordre chronologique. Étant donné que Firebase génère une clé unique pour chaque article de blog, aucun conflit d'écriture ne se produira si plusieurs utilisateurs ajoutent un article en même temps. Les données de votre base de données ressemblent maintenant à ceci :

{
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "-JRHTHaKuITFIhnj02kE": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

En JavaScript, Python et Go, le modèle consistant à appeler push() puis à appeler immédiatement set() est si courant que le SDK Firebase vous permet de les combiner en transmettant les données à définir directement à push() comme suit :

Java
// No Java equivalent
Noeud.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'
})
Aller
if _, err := postsRef.Push(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

Obtenir la clé unique générée par push()

L'appel de push() renverra une référence au nouveau chemin de données, que vous pourrez utiliser pour obtenir la clé ou y définir des données. Le code suivant produira les mêmes données que l'exemple ci-dessus, mais nous aurons désormais accès à la clé unique qui a été générée :

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();
Noeud.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
Aller
// 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

Comme vous pouvez le voir, vous pouvez obtenir la valeur de la clé unique à partir de votre référence push() .

Dans la section suivante sur la récupération de données , nous apprendrons comment lire ces données à partir d'une base de données Firebase.

Sauvegarde des données transactionnelles

Lorsque vous travaillez avec des données complexes susceptibles d'être corrompues par des modifications simultanées, telles que des compteurs incrémentiels, le SDK fournit une opération de transaction .

En Java et Node.js, vous donnez à l'opération de transaction deux rappels : une fonction de mise à jour et un rappel d'achèvement facultatif. En Python et Go, l'opération de transaction est bloquante et n'accepte donc que la fonction de mise à jour.

La fonction de mise à jour prend l'état actuel des données comme argument et doit renvoyer le nouvel état souhaité que vous souhaitez écrire. Par exemple, si vous souhaitez augmenter le nombre de votes positifs sur un article de blog spécifique, vous écrirez une transaction comme celle-ci :

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");
  }
});
Noeud.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')
Aller
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)
}

L'exemple ci-dessus vérifie si le compteur est null ou n'a pas encore été incrémenté, car les transactions peuvent être appelées avec null si aucune valeur par défaut n'a été écrite.

Si le code ci-dessus avait été exécuté sans fonction de transaction et que deux clients tentaient de l'incrémenter simultanément, ils écriraient tous deux 1 comme nouvelle valeur, ce qui entraînerait un incrément au lieu de deux.

Connectivité réseau et écritures hors ligne

Les clients Firebase Node.js et Java conservent leur propre version interne de toutes les données actives. Lorsque des données sont écrites, elles sont d’abord écrites dans cette version locale. Le client synchronise ensuite ces données avec la base de données et avec d'autres clients dans la mesure du possible.

Par conséquent, toutes les écritures dans la base de données déclencheront immédiatement des événements locaux, avant même que des données n’aient été écrites dans la base de données. Cela signifie que lorsque vous écrivez une application à l'aide de Firebase, votre application restera réactive quelle que soit la latence du réseau ou la connectivité Internet.

Une fois la connectivité rétablie, nous recevrons l'ensemble d'événements approprié afin que le client « rattrape » l'état actuel du serveur, sans avoir à écrire de code personnalisé.

Sécuriser vos données

La base de données Firebase Realtime Database dispose d'un langage de sécurité qui vous permet de définir quels utilisateurs ont un accès en lecture et en écriture aux différents nœuds de vos données. Vous pouvez en savoir plus à ce sujet dans Sécurisez vos données .