获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

Guardar datos

Este documento cubre los cuatro métodos para escribir datos en su base de datos en tiempo real de Firebase: configuración, actualización, inserción y compatibilidad con transacciones.

Formas de guardar datos

establecer Escriba o reemplace datos en una ruta definida , como messages/users/<username>
actualizar Actualice algunas de las claves para una ruta definida sin reemplazar todos los datos
empujar Agregar a una lista de datos en la base de datos. Cada vez que inserta un nuevo nodo en una lista, su base de datos genera una clave única, como messages/users/<unique-user-id>/<username>
transacción Use transacciones cuando trabaje con datos complejos que podrían corromperse con actualizaciones simultáneas

Guardar datos

La operación básica de escritura de la base de datos es un conjunto que guarda nuevos datos en la referencia de la base de datos especificada, reemplazando cualquier dato existente en esa ruta. Para comprender el conjunto, crearemos una aplicación de blog simple. Los datos de su aplicación se almacenan en esta referencia de base de datos:

Java
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Nodo.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');
Pitón
# Import database module.
from firebase_admin import db

# Get a database reference to our blog.
ref = db.reference('server/saving-data/fireblog')
Vamos
// 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")

Comencemos guardando algunos datos de usuario. Almacenaremos a cada usuario por un nombre de usuario único y también almacenaremos su nombre completo y fecha de nacimiento. Dado que cada usuario tendrá un nombre de usuario único, tiene sentido usar el método set aquí en lugar del método push, ya que ya tiene la clave y no necesita crear una.

Primero, cree una referencia de base de datos a sus datos de usuario. Luego use set() / setValue() para guardar un objeto de usuario en la base de datos con el nombre de usuario, el nombre completo y la fecha de nacimiento del usuario. Puede pasar establecer una cadena, número, booleano, null , matriz o cualquier objeto JSON. Pasar null eliminará los datos en la ubicación especificada. En este caso, le pasarás un objeto:

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);
Nodo.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'
  }
});
Pitón
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'
    }
})
Vamos

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

Cuando un objeto JSON se guarda en la base de datos, las propiedades del objeto se asignan automáticamente a las ubicaciones secundarias de la base de datos de forma anidada. Ahora, si navega a la URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name , veremos el valor "Alan Turing". También puede guardar datos directamente en una ubicación secundaria:

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

Los dos ejemplos anteriores (escribir ambos valores al mismo tiempo como un objeto y escribirlos por separado en ubicaciones secundarias) darán como resultado que se guarden los mismos datos en su base de datos:

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

El primer ejemplo solo activará un evento en los clientes que están viendo los datos, mientras que el segundo ejemplo activará dos. Es importante tener en cuenta que si los datos ya existían en usersRef , el primer enfoque los sobrescribiría, pero el segundo método solo modificaría el valor de cada nodo secundario separado y dejaría sin cambios a otros elementos secundarios de usersRef .

Actualización de datos guardados

Si desea escribir en varios elementos secundarios de una ubicación de base de datos al mismo tiempo sin sobrescribir otros nodos secundarios, puede usar el método de actualización como se muestra a continuación:

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

hopperRef.updateChildrenAsync(hopperUpdates);
Nodo.js
const usersRef = ref.child('users');
const hopperRef = usersRef.child('gracehop');
hopperRef.update({
  'nickname': 'Amazing Grace'
});
Pitón
hopper_ref = users_ref.child('gracehop')
hopper_ref.update({
    'nickname': 'Amazing Grace'
})
Vamos
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

Esto actualizará los datos de Grace para incluir su apodo. Si hubiera utilizado establecer aquí en lugar de actualizar, habría eliminado tanto full_name como date_of_birth de su hopperRef .

Firebase Realtime Database también admite actualizaciones de rutas múltiples. Esto significa que la actualización ahora puede actualizar valores en múltiples ubicaciones en su base de datos al mismo tiempo, una característica poderosa que le ayuda a desnormalizar sus datos . Usando actualizaciones de rutas múltiples, puede agregar apodos a Grace y Alan al mismo tiempo:

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

usersRef.updateChildrenAsync(userUpdates);
Nodo.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome/nickname': 'Alan The Machine',
  'gracehop/nickname': 'Amazing Grace'
});
Pitón
users_ref.update({
    'alanisawesome/nickname': 'Alan The Machine',
    'gracehop/nickname': 'Amazing Grace'
})
Vamos
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)
}

Después de esta actualización, tanto a Alan como a Grace se les agregaron sus apodos:

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

Tenga en cuenta que tratar de actualizar objetos escribiendo objetos con las rutas incluidas dará como resultado un comportamiento diferente. Echemos un vistazo a lo que sucede si, en cambio, intenta actualizar a Grace y Alan de esta manera:

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);
Nodo.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome': {
    'nickname': 'Alan The Machine'
  },
  'gracehop': {
    'nickname': 'Amazing Grace'
  }
});
Pitón
users_ref.update({
    'alanisawesome': {
        'nickname': 'Alan The Machine'
    },
    'gracehop': {
        'nickname': 'Amazing Grace'
    }
})
Vamos
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)
}

Esto da como resultado un comportamiento diferente, es decir, sobrescribir todo el nodo /users :

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

Agregar una devolución de llamada de finalización

En Node.js y Java Admin SDK, si desea saber cuándo se confirmaron sus datos, puede agregar una devolución de llamada de finalización. Tanto los métodos de configuración como los de actualización en estos SDK toman una devolución de llamada de finalización opcional que se llama cuando la escritura se ha confirmado en la base de datos. Si la llamada no tuvo éxito por alguna razón, la devolución de llamada recibe un objeto de error que indica por qué ocurrió la falla. En los SDK de Python y Go Admin, todos los métodos de escritura están bloqueados. Es decir, los métodos de escritura no regresan hasta que las escrituras se confirman en la base de datos.

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

Guardar listas de datos

Al crear listas de datos, es importante tener en cuenta la naturaleza multiusuario de la mayoría de las aplicaciones y ajustar la estructura de la lista en consecuencia. Ampliando el ejemplo anterior, agreguemos publicaciones de blog a su aplicación. Su primer instinto podría ser usar set para almacenar elementos secundarios con índices enteros de incremento automático, como el siguiente:

// 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 usuario agrega una nueva publicación, se almacenará como /posts/2 . Esto funcionaría si solo un autor estuviera agregando publicaciones, pero en su aplicación de blogs colaborativos muchos usuarios pueden agregar publicaciones al mismo tiempo. Si dos autores escriben en /posts/2 simultáneamente, el otro eliminará una de las publicaciones.

Para resolver esto, los clientes de Firebase proporcionan una función push() que genera una clave única para cada nuevo elemento secundario . Mediante el uso de claves secundarias únicas, varios clientes pueden agregar elementos secundarios a la misma ubicación al mismo tiempo sin preocuparse por los conflictos de escritura.

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"));
Nodo.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'
});
Pitón
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'
})
Vamos

// 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 clave única se basa en una marca de tiempo, por lo que los elementos de la lista se ordenarán automáticamente cronológicamente. Debido a que Firebase genera una clave única para cada publicación de blog, no se producirán conflictos de escritura si varios usuarios agregan una publicación al mismo tiempo. Los datos de su base de datos ahora se ven así:

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

En JavaScript, Python y Go, el patrón de llamar a push() y luego llamar inmediatamente a set() es tan común que el SDK de Firebase le permite combinarlos pasando los datos que se configurarán directamente a push() de la siguiente manera:

Java
// No Java equivalent
Nodo.js
// This is equivalent to the calls to push().set(...) above
postsRef.push({
  author: 'gracehop',
  title: 'Announcing COBOL, a New Programming Language'
});;
Pitón
# This is equivalent to the calls to push().set(...) above
posts_ref.push({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})
Vamos
if _, err := postsRef.Push(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

Obtener la clave única generada por push()

Llamar push() devolverá una referencia a la nueva ruta de datos, que puede usar para obtener la clave o establecer datos en ella. El siguiente código dará como resultado los mismos datos que el ejemplo anterior, pero ahora tendremos acceso a la clave única que se generó:

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();
Nodo.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;
Pitón
# 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
Vamos
// 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

Como puede ver, puede obtener el valor de la clave única de su referencia push() .

En la siguiente sección sobre Recuperación de datos , aprenderemos a leer estos datos de una base de datos de Firebase.

Guardar datos transaccionales

Cuando se trabaja con datos complejos que podrían corromperse por modificaciones simultáneas, como contadores incrementales, el SDK proporciona una operación de transacción .

En Java y Node.js, le da a la operación de transacción dos devoluciones de llamada: una función de actualización y una devolución de llamada de finalización opcional. En Python y Go, la operación de transacción se bloquea y, por lo tanto, solo acepta la función de actualización.

La función de actualización toma el estado actual de los datos como argumento y debe devolver el nuevo estado deseado que le gustaría escribir. Por ejemplo, si quisiera incrementar la cantidad de votos a favor en una publicación de blog específica, escribiría una transacción como la siguiente:

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");
  }
});
Nodo.js
const upvotesRef = db.ref('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes');
upvotesRef.transaction((current_value) => {
  return (current_value || 0) + 1;
});
Pitón
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')
Vamos
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)
}

El ejemplo anterior verifica si el contador es null o aún no se ha incrementado, ya que las transacciones se pueden llamar con null si no se escribió un valor predeterminado.

Si el código anterior se hubiera ejecutado sin una función de transacción y dos clientes intentaran incrementarlo simultáneamente, ambos escribirían 1 como el nuevo valor, lo que daría como resultado un incremento en lugar de dos.

Conectividad de red y escrituras sin conexión

Los clientes de Firebase Node.js y Java mantienen su propia versión interna de cualquier dato activo. Cuando se escriben datos, primero se escriben en esta versión local. Luego, el cliente sincroniza esos datos con la base de datos y con otros clientes en base al "mejor esfuerzo".

Como resultado, todas las escrituras en la base de datos desencadenarán eventos locales inmediatamente, incluso antes de que se escriba ningún dato en la base de datos. Esto significa que cuando escribe una aplicación con Firebase, su aplicación seguirá respondiendo independientemente de la latencia de la red o la conectividad a Internet.

Una vez que se restablezca la conectividad, recibiremos el conjunto de eventos adecuado para que el cliente "se ponga al día" con el estado actual del servidor, sin tener que escribir ningún código personalizado.

Protección de sus datos

Firebase Realtime Database tiene un lenguaje de seguridad que le permite definir qué usuarios tienen acceso de lectura y escritura a diferentes nodos de sus datos. Puede obtener más información al respecto en Proteja sus datos .