Enregistrement des données

Ce document présente les quatre méthodes d'écriture de données dans votre Firebase Realtime Database: set, update, push et prise en charge des transactions.

Économiser des données

set Écrire ou remplacer des données dans un chemin d'accès défini, comme messages/users/<username>
update Mettre à jour certaines des clés d'un chemin d'accès défini sans remplacer toutes les données
transmettre Ajouter à une liste de données dans la base de données. Chaque fois que vous ajoutez un nouveau nœud à 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.

Enregistrer des données

L'opération d'écriture de base de données de base est un ensemble qui enregistre de 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 d'accès. Pour comprendre le concept de set, nous allons créer une application de blog simple. Les données de votre application sont stockées à cette référence de base de données:

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')
Accéder
// 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 des données utilisateur. Nous stockons chaque utilisateur avec un nom d'utilisateur unique, ainsi que son nom complet et sa date de naissance. Étant donné que chaque utilisateur dispose d'un nom d'utilisateur unique, il est logique d'utiliser la méthode set ici plutôt que la méthode push, car vous disposez déjà de la clé et n'avez pas besoin d'en créer une.

Commencez par créer une référence de base de données pour vos données utilisateur. Utilisez ensuite set() / setValue() pour enregistrer un objet utilisateur dans la base de données avec son nom d'utilisateur, son nom complet et son anniversaire. Vous pouvez transmettre une chaîne, un nombre, une valeur booléenne, un null, un tableau ou un objet JSON. Si vous transmettez null, les données seront supprimées à l'emplacement spécifié. Dans ce cas, vous lui transmettrez 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);
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'
    }
})
Accéder
// 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 de manière imbriquée sur les emplacements enfants de la base de données. Si vous accédez à l'URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name, la valeur "Alan Turing" s'affiche. Vous pouvez également enregistrer des 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"));
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'
})
Accéder
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éclenche qu'un seul événement sur les clients qui surveillent les données, tandis que le second en déclenche deux. Il est important de noter que si des données existaient déjà dans usersRef, la première approche les écraserait, mais que la deuxième méthode ne modifierait que la valeur de chaque nœud enfant distinct, tout en laissant les autres enfants de usersRef inchangés.

Mettre à jour les données enregistrées

Si vous souhaitez écrire simultanément dans plusieurs enfants d'un emplacement de base de données sans écraser d'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);
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'
})
Accéder
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

Les données de Grace seront mises à jour pour inclure son surnom. Si vous aviez utilisé "set" ici au lieu de "update", full_name et date_of_birth auraient été supprimés de votre hopperRef.

Firebase Realtime Database est également compatible avec les mises à jour multi-chemins. Cela signifie que la mise à jour peut désormais mettre à jour des valeurs à plusieurs endroits de votre base de données en même temps. Il s'agit d'une fonctionnalité puissante qui vous permet de dénormaliser vos données. Grâce aux mises à jour multicanaux, 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);
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'
})
Accéder
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, les surnoms d'Alan et de Grace ont été 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 que le comportement sera différent si vous essayez de mettre à jour des objets en écrivant des objets avec les chemins inclus. Voyons ce qui se passe si vous essayez plutôt de mettre à jour Grace et Alan de cette manière:

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'
    }
})
Accéder
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"
    }
  }
}

Ajouter un rappel de fin

Dans les SDK d'administration Node.js et Java, si vous souhaitez savoir quand vos données ont été validées, vous pouvez ajouter un rappel de fin. Les méthodes set et update de ces SDK acceptent un rappel de finalisation 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, un objet d'erreur indiquant pourquoi l'échec s'est produit est transmis au rappel. Dans les SDK Admin Python et Go, toutes les méthodes d'écriture sont bloquantes. Autrement dit, les méthodes d'écriture ne renvoient rien tant que les écritures n'ont pas été 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.");
    }
  }
});
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.');
  }
});

Enregistrer des listes de données

Lorsque vous créez des listes de données, il est important de garder à l'esprit la nature multi-utilisateur de la plupart des applications et d'ajuster la structure de votre liste en conséquence. Pour développer l'exemple ci-dessus, ajoutons des articles de blog à votre application. Votre premier réflexe pourrait être d'utiliser un ensemble pour stocker des enfants avec des index entiers incrémentiels automatiques, 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 un post, il sera stocké en tant que /posts/2. Cela fonctionnerait si un seul auteur ajoutait des posts, mais dans votre application de blog collaboratif, de nombreux utilisateurs peuvent ajouter des posts en même temps. Si deux auteurs écrivent simultanément dans /posts/2, l'un des posts 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 nouvel enfant. En utilisant des clés enfants uniques, plusieurs clients peuvent ajouter des enfants au même emplacement en même temps sans avoir à 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"));
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'
})
Accéder
// 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 code temporel. Les éléments de la liste sont donc automatiquement trié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 produit si plusieurs utilisateurs ajoutent un article en même temps. Vos données de base de données se présentent désormais comme suit:

{
  "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 d'appel de push(), puis d'appel immédiat de 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
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'
})
Accéder
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() renvoie une référence au nouveau chemin de données, que vous pouvez utiliser pour obtenir la clé ou y définir des données. Le code suivant génère les mêmes données que l'exemple ci-dessus, mais nous avons désormais accès à la clé unique 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();
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
Accéder
// 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 constater, vous pouvez obtenir la valeur de la clé unique à partir de votre référence push().

Dans la section suivante intitulée Récupérer des données, vous allez apprendre à lire ces données à partir d'une base de données Firebase.

Enregistrer les données transactionnelles

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

En Java et en Node.js, vous fournissez à l'opération de transaction deux rappels: une fonction de mise à jour et un rappel de fin facultatif. En Python et en 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 l'état souhaité que vous souhaitez écrire. Par exemple, si vous souhaitez incrémenter le nombre de votes positifs sur un article de blog spécifique, vous devez écrire une transaction comme suit:

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')
Accéder
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 s'il 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 avaient tenté de l'incrémenter simultanément, ils auraient tous deux écrit 1 comme nouvelle valeur, ce qui aurait entraîné un incrément au lieu de deux.

Connectivité réseau et écritures hors connexion

Les clients Firebase Node.js et Java gèrent leur propre version interne de toutes les données actives. Lorsque des données sont écrites, elles le sont d'abord 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éclenchent 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, elle reste réactive quelle que soit la latence du réseau ou la connexion 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

Firebase Realtime Database dispose d'un langage de sécurité qui vous permet de définir les utilisateurs disposant d'un accès en lecture et en écriture aux différents nœuds de vos données. Pour en savoir plus, consultez Protéger vos données.